|
1 | 1 | ## Self-hosting Comp (Apps + Portal) |
2 | 2 |
|
3 | | -This guide walks you through running the Comp app and portal with Docker. |
| 3 | +This file is a brief overview for Docker-based self-hosting. |
4 | 4 |
|
5 | | -### Overview |
| 5 | +**For the detailed, up-to-date guide, see:** |
6 | 6 |
|
7 | | -- You will run two services: `app` (primary) and `portal` (customer portal). |
8 | | -- You must bring your own externally hosted PostgreSQL database. The stack does not run a local DB in production mode. |
9 | | -- You must provide email (Resend) and Trigger.dev credentials for email login and automated workflows. |
| 7 | +- [Docker Self-Hosting Guide](https://trycomp.ai/docs/self-hosting/docker) |
| 8 | +- [Environment Reference](https://trycomp.ai/docs/self-hosting/env-reference) |
10 | 9 |
|
11 | | -### Prerequisites |
12 | | - |
13 | | -- Docker Desktop (or Docker Engine) installed |
14 | | -- Externally hosted PostgreSQL 14+ (e.g., DigitalOcean, Neon, RDS) with SSL |
15 | | -- Resend account and API key for transactional email (magic links, OTP) |
16 | | -- Trigger.dev account and project for automated workflows |
17 | | - |
18 | | -### Required environment variables |
19 | | - |
20 | | -Set these in `docker-compose.yml` under each service as shown below. |
21 | | - |
22 | | -App (`apps/app`): |
23 | | - |
24 | | -- `DATABASE_URL` (required): External Postgres URL. Example: `postgresql://user:pass@host:5432/db?sslmode=require` |
25 | | -- `AUTH_SECRET` (required): 32-byte base64. Generate with `openssl rand -base64 32` |
26 | | -- `RESEND_API_KEY` (required): From Resend dashboard |
27 | | -- `REVALIDATION_SECRET` (required): Any random string |
28 | | -- `BETTER_AUTH_URL` (required): Base URL of the app server (e.g., `http://localhost:3000`) |
29 | | -- `NEXT_PUBLIC_BETTER_AUTH_URL` (required): Same as above for client code |
30 | | -- `NEXT_PUBLIC_PORTAL_URL` (required): Base URL of the portal server (e.g., `http://localhost:3002`) |
31 | | -- `TRIGGER_SECRET_KEY` (required for workflows): From Trigger.dev project settings |
32 | | -- Optional (infrastructure): `UPSTASH_REDIS_REST_URL`, `UPSTASH_REDIS_REST_TOKEN` |
33 | | - |
34 | | -Portal (`apps/portal`): |
35 | | - |
36 | | -- `DATABASE_URL` (required): Same external Postgres URL |
37 | | -- `BETTER_AUTH_SECRET` (required): A secret used by portal auth (distinct from app `AUTH_SECRET`) |
38 | | -- `BETTER_AUTH_URL` (required): Base URL of the portal (e.g., `http://localhost:3002`) |
39 | | -- `NEXT_PUBLIC_BETTER_AUTH_URL` (required): Same as portal base URL for client code |
40 | | -- `RESEND_API_KEY` (required): Same Resend key |
41 | | - |
42 | | -### Optional environment variables |
43 | | - |
44 | | -App (`apps/app`): |
45 | | - |
46 | | -- **APP_AWS_REGION**, **APP_AWS_ACCESS_KEY_ID**, **APP_AWS_SECRET_ACCESS_KEY**, **APP_AWS_BUCKET_NAME**: AWS S3 credentials for file storage (attachments, general uploads). |
47 | | -- **APP_AWS_QUESTIONNAIRE_UPLOAD_BUCKET**: AWS S3 bucket name specifically for questionnaire file uploads. Required for the Security Questionnaire feature. If not set, users will see an error when trying to parse questionnaires. |
48 | | -- **APP_AWS_KNOWLEDGE_BASE_BUCKET**: AWS S3 bucket name specifically for knowledge base documents. Required for the Knowledge Base feature in Security Questionnaire. If not set, users will see an error when trying to upload knowledge base documents. |
49 | | -- **APP_AWS_ORG_ASSETS_BUCKET**: AWS S3 bucket name for organization static assets (e.g., company logos, compliance certificates). Required for logo uploads in organization settings and Trust Portal compliance certificate uploads. If not set, these features will fail. |
50 | | -- **OPENAI_API_KEY**: Enables AI features that call OpenAI models. |
51 | | -- **UPSTASH_REDIS_REST_URL**, **UPSTASH_REDIS_REST_TOKEN**: Optional Redis (Upstash) used for rate limiting/queues/caching. |
52 | | -- **NEXT_PUBLIC_POSTHOG_KEY**, **NEXT_PUBLIC_POSTHOG_HOST**: Client analytics via PostHog; leave unset to disable. |
53 | | -- **NEXT_PUBLIC_GTM_ID**: Google Tag Manager container ID for client tracking. |
54 | | -- **NEXT_PUBLIC_LINKEDIN_PARTNER_ID**, **NEXT_PUBLIC_LINKEDIN_CONVERSION_ID**: LinkedIn insights/conversion tracking. |
55 | | -- **NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL**: Google Ads conversion tracking label. |
56 | | -- **DUB_API_KEY**, **DUB_REFER_URL**: Dub.co link shortener/referral features. |
57 | | -- **FIRECRAWL_API_KEY**: Optional LLM/crawling providers for research features. |
58 | | -- **SLACK_SALES_WEBHOOK**: Slack webhook for sales/lead notifications. |
59 | | -- **GA4_API_SECRET**, **GA4_MEASUREMENT_ID**: Google Analytics 4 server/client tracking. |
60 | | -- **NEXT_PUBLIC_API_URL**: Override client API base URL (defaults to same origin). |
61 | | - |
62 | | -API (`apps/api`): |
63 | | - |
64 | | -- **APP_AWS_REGION**, **APP_AWS_ACCESS_KEY_ID**, **APP_AWS_SECRET_ACCESS_KEY**, **APP_AWS_BUCKET_NAME**: AWS S3 credentials for file storage (attachments, general uploads). |
65 | | -- **APP_AWS_QUESTIONNAIRE_UPLOAD_BUCKET**: AWS S3 bucket name specifically for questionnaire file uploads. Required for the Security Questionnaire feature. |
66 | | -- **APP_AWS_KNOWLEDGE_BASE_BUCKET**: AWS S3 bucket name specifically for knowledge base documents. Required for the Knowledge Base feature in Security Questionnaire. |
67 | | -- **APP_AWS_ORG_ASSETS_BUCKET**: AWS S3 bucket name for organization static assets (e.g., company logos, compliance certificates). Required for Trust Portal compliance certificate uploads and organization logo uploads. If not set, these features will fail. |
68 | | -- **OPENAI_API_KEY**: Enables AI features that call OpenAI models. |
69 | | -- **UPSTASH_VECTOR_REST_URL**, **UPSTASH_VECTOR_REST_TOKEN**: Required for vector database operations (questionnaire auto-answer, SOA auto-fill, knowledge base search). |
70 | | -- **BETTER_AUTH_URL**: URL of the Better Auth instance (usually the same as the app URL). |
71 | | -- **DATABASE_URL**: PostgreSQL database connection string. |
72 | | - |
73 | | -Portal (`apps/portal`): |
| 10 | +### Quick Summary |
74 | 11 |
|
75 | | -- **NEXT_PUBLIC_POSTHOG_KEY**, **NEXT_PUBLIC_POSTHOG_HOST**: Client analytics via PostHog for portal. |
76 | | -- **UPSTASH_REDIS_REST_URL**, **UPSTASH_REDIS_REST_TOKEN**: Optional Redis if you enable portal-side rate limiting/queues. |
| 12 | +Docker uses **separate env files** (not a root `.env`): |
77 | 13 |
|
78 | | -### docker-compose.yml uses `.env` (no direct edits needed) |
| 14 | +| File | Services | |
| 15 | +|------|----------| |
| 16 | +| `packages/db/.env` | migrator, seeder | |
| 17 | +| `apps/app/.env` | app | |
| 18 | +| `apps/portal/.env` | portal | |
79 | 19 |
|
80 | | -We keep `docker-compose.yml` generic and read values from `.env`: |
| 20 | +### Minimal Required Environment |
81 | 21 |
|
82 | | -```yaml |
83 | | -services: |
84 | | - migrator: |
85 | | - build: |
86 | | - context: . |
87 | | - dockerfile: Dockerfile |
88 | | - target: migrator |
89 | | - env_file: |
90 | | - - .env |
| 22 | +For a functional deployment: |
91 | 23 |
|
92 | | - seeder: |
93 | | - build: |
94 | | - context: . |
95 | | - dockerfile: Dockerfile |
96 | | - target: migrator |
97 | | - env_file: |
98 | | - - .env |
99 | | - command: sh -lc "bunx prisma generate --schema=node_modules/@trycompai/db/dist/schema.prisma && bun packages/db/prisma/seed/seed.js" |
100 | | - |
101 | | - app: |
102 | | - build: |
103 | | - context: . |
104 | | - dockerfile: Dockerfile |
105 | | - target: app |
106 | | - args: |
107 | | - NEXT_PUBLIC_BETTER_AUTH_URL: ${BETTER_AUTH_URL} |
108 | | - ports: ['3000:3000'] |
109 | | - env_file: [.env] |
110 | | - restart: unless-stopped |
111 | | - healthcheck: |
112 | | - test: ['CMD-SHELL', 'curl -f http://localhost:3000/api/health || exit 1'] |
113 | | - interval: 30s |
114 | | - timeout: 10s |
115 | | - retries: 3 |
116 | | - |
117 | | - portal: |
118 | | - build: |
119 | | - context: . |
120 | | - dockerfile: Dockerfile |
121 | | - target: portal |
122 | | - args: |
123 | | - NEXT_PUBLIC_BETTER_AUTH_URL: ${BETTER_AUTH_URL_PORTAL} |
124 | | - ports: ['3002:3000'] |
125 | | - env_file: [.env] |
126 | | - restart: unless-stopped |
127 | | - healthcheck: |
128 | | - test: ['CMD-SHELL', 'curl -f http://localhost:3002/ || exit 1'] |
129 | | - interval: 30s |
130 | | - timeout: 10s |
131 | | - retries: 3 |
132 | | -``` |
133 | | -
|
134 | | -#### `.env` example |
135 | | - |
136 | | -Create a `.env` file at the repo root with your values (never commit real secrets): |
137 | | - |
138 | | -```bash |
139 | | -# External PostgreSQL (required) |
140 | | -DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require |
141 | | -
|
142 | | -# App auth + URLs (required) |
143 | | -AUTH_SECRET= |
144 | | -BETTER_AUTH_URL=http://localhost:3000 |
145 | | -NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3000 |
146 | | -NEXT_PUBLIC_PORTAL_URL=http://localhost:3002 |
147 | | -REVALIDATION_SECRET= |
148 | | -
|
149 | | -# Email (required) |
150 | | -RESEND_API_KEY= |
151 | | -
|
152 | | -# Workflows (Trigger.dev hosted) |
153 | | -TRIGGER_SECRET_KEY= |
154 | | -
|
155 | | -# Portal auth + URLs (required) |
156 | | -BETTER_AUTH_SECRET= |
157 | | -BETTER_AUTH_URL_PORTAL=http://localhost:3002 |
158 | | -NEXT_PUBLIC_BETTER_AUTH_URL_PORTAL=http://localhost:3002 |
159 | | -
|
160 | | -# Optional |
161 | | -# AWS S3 (for file storage) |
162 | | -# APP_AWS_REGION= |
163 | | -# APP_AWS_ACCESS_KEY_ID= |
164 | | -# APP_AWS_SECRET_ACCESS_KEY= |
165 | | -# APP_AWS_BUCKET_NAME= |
166 | | -# APP_AWS_QUESTIONNAIRE_UPLOAD_BUCKET= |
167 | | -# APP_AWS_KNOWLEDGE_BASE_BUCKET= |
168 | | -# APP_AWS_ORG_ASSETS_BUCKET= |
169 | | -# OPENAI_API_KEY= |
170 | | -# UPSTASH_REDIS_REST_URL= |
171 | | -# UPSTASH_REDIS_REST_TOKEN= |
172 | | -# NEXT_PUBLIC_POSTHOG_KEY= |
173 | | -# NEXT_PUBLIC_POSTHOG_HOST= |
174 | | -# NEXT_PUBLIC_GTM_ID= |
175 | | -# NEXT_PUBLIC_LINKEDIN_PARTNER_ID= |
176 | | -# NEXT_PUBLIC_LINKEDIN_CONVERSION_ID= |
177 | | -# NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL= |
178 | | -# DUB_API_KEY= |
179 | | -# DUB_REFER_URL= |
180 | | -# FIRECRAWL_API_KEY= |
181 | | -# SLACK_SALES_WEBHOOK= |
182 | | -# GA4_API_SECRET= |
183 | | -# GA4_MEASUREMENT_ID= |
184 | | -# NEXT_PUBLIC_API_URL= |
185 | | -``` |
| 24 | +- **Database**: `DATABASE_URL` in all three env files |
| 25 | +- **Auth**: `AUTH_SECRET`, `SECRET_KEY`, `BETTER_AUTH_URL`, `NEXT_PUBLIC_BETTER_AUTH_URL` (app); `BETTER_AUTH_SECRET` (portal) |
| 26 | +- **Email**: `RESEND_API_KEY` in app and portal |
| 27 | +- **Workflows**: `TRIGGER_SECRET_KEY` in app |
| 28 | +- **Misc**: `REVALIDATION_SECRET`, `NEXT_PUBLIC_PORTAL_URL` in app |
186 | 29 |
|
187 | | -#### What the `migrator` and `seeder` services do |
188 | | - |
189 | | -- **migrator**: Runs `prisma migrate deploy` using the combined schema from `@trycompai/db`. |
190 | | - - Purpose: create/update tables, indexes, and constraints in your hosted Postgres. |
191 | | - - Safe to run repeatedly (Prisma applies only pending migrations). |
192 | | - |
193 | | -- **seeder**: Generates a Prisma client from the same combined schema and executes the app’s seed script. |
194 | | - - Purpose: load application reference data (frameworks, controls, relations). |
195 | | - - Behavior: idempotent upserts by `id`. It does not delete rows; existing rows with matching ids are updated, and relations are connected if missing. |
196 | | - |
197 | | -Notes: |
198 | | - |
199 | | -- The stack migrates with `@trycompai/db` combined Prisma schema and then seeds. Seeding is idempotent: records are upserted by id and relations are connected; nothing is deleted. |
200 | | -- Ensure your DB user has privileges to create/alter tables in the target database. |
201 | | - |
202 | | -### Trigger.dev (hosted runner) |
203 | | - |
204 | | -Trigger.dev powers AI automations and background workflows. |
205 | | - |
206 | | -Steps: |
207 | | - |
208 | | -1. Create an account at `https://cloud.trigger.dev` |
209 | | -2. Create a project and copy `TRIGGER_SECRET_KEY` |
210 | | -3. From your workstation (not inside Docker): |
211 | | - ```bash |
212 | | - cd apps/app |
213 | | - bunx trigger.dev@latest login |
214 | | - bunx trigger.dev@latest deploy |
215 | | - ``` |
216 | | -4. Set `TRIGGER_SECRET_KEY` in the `app` service environment. |
217 | | - |
218 | | -### Resend (email) |
219 | | - |
220 | | -- Create a Resend account and get `RESEND_API_KEY` |
221 | | -- Add a domain if you plan to send emails from a custom domain |
222 | | -- Set `RESEND_API_KEY` in both `app` and `portal` services |
223 | | - |
224 | | -### Build & run |
225 | | - |
226 | | -#### Prepare environment |
227 | | - |
228 | | -Copy the example and fill real values (kept out of git): |
| 30 | +### Prerequisites |
229 | 31 |
|
230 | | -```bash |
231 | | -cp .env.example .env |
232 | | -# edit .env with your production secrets and URLs |
233 | | -``` |
| 32 | +- Docker Desktop or Docker Engine |
| 33 | +- External PostgreSQL 14+ with SSL |
| 34 | +- [Resend](https://resend.com) account for email |
| 35 | +- [Trigger.dev](https://cloud.trigger.dev) account for workflows |
234 | 36 |
|
235 | | -#### Fresh install (optional clean): |
| 37 | +### Build & Run |
236 | 38 |
|
237 | 39 | ```bash |
238 | | -docker compose down --rmi all --volumes --remove-orphans |
239 | | -docker builder prune --all --force |
240 | | -``` |
| 40 | +# 1. Create env files from examples |
| 41 | +cp packages/db/.env.example packages/db/.env |
| 42 | +cp apps/app/.env.example apps/app/.env |
| 43 | +cp apps/portal/.env.example apps/portal/.env |
| 44 | +# Edit each with your production values |
241 | 45 |
|
242 | | -#### Build images: |
| 46 | +# 2. Export build args |
| 47 | +export BETTER_AUTH_URL="https://app.yourdomain.com" |
| 48 | +export BETTER_AUTH_URL_PORTAL="https://portal.yourdomain.com" |
243 | 49 |
|
244 | | -```bash |
| 50 | +# 3. Build |
245 | 51 | docker compose build --no-cache |
246 | | -``` |
247 | | - |
248 | | -#### Run migrations & seed (against your hosted DB): |
249 | 52 |
|
250 | | -```bash |
| 53 | +# 4. Migrate & seed |
251 | 54 | docker compose run --rm migrator |
252 | 55 | docker compose run --rm seeder |
| 56 | + |
| 57 | +# 5. Start |
| 58 | +docker compose up -d app portal |
253 | 59 | ``` |
254 | 60 |
|
255 | | -#### Start the apps: |
| 61 | +### Trigger.dev Deployment |
| 62 | + |
| 63 | +Deploy tasks from your workstation (not inside Docker): |
256 | 64 |
|
257 | 65 | ```bash |
258 | | -docker compose up -d app portal |
| 66 | +cd apps/app |
| 67 | +bunx trigger.dev@latest login |
| 68 | +bunx trigger.dev@latest deploy |
259 | 69 | ``` |
260 | 70 |
|
261 | | -Verify health: |
| 71 | +### Troubleshooting |
| 72 | + |
| 73 | +View logs to debug missing env vars: |
262 | 74 |
|
263 | 75 | ```bash |
264 | | -curl -s http://localhost:3000/api/health |
| 76 | +docker compose logs app |
| 77 | +docker compose logs portal |
265 | 78 | ``` |
266 | 79 |
|
267 | | -### Production tips |
| 80 | +The Dockerfile sets `SKIP_ENV_VALIDATION=true` at build time, so missing variables only cause errors at runtime. |
268 | 81 |
|
269 | | -- Set real domains and HTTPS (behind a reverse proxy / load balancer) |
270 | | -- Update `BETTER_AUTH_URL`, `NEXT_PUBLIC_BETTER_AUTH_URL`, and portal equivalents to the public domains |
271 | | -- Use strong secrets and rotate them periodically |
272 | | -- Ensure the hosted Postgres requires SSL and restricts network access (VPC, IP allowlist, or private networking) |
| 82 | +See the [full troubleshooting guide](https://trycomp.ai/docs/self-hosting/docker#troubleshooting) for common issues. |
0 commit comments