Overview
This guide covers running Comp with Docker Compose. The stack includes:
| Service | Purpose | Port |
|---|---|---|
migrator |
Runs Prisma migrations against your database | - |
seeder |
Loads reference data (frameworks, controls) | - |
app |
Main Comp application | 3000 |
portal |
Customer trust portal | 3002 |
Prerequisites
- Docker Desktop or Docker Engine installed
- External PostgreSQL 14+ with SSL (e.g., Neon, DigitalOcean, RDS)
- Resend account for transactional email
- Trigger.dev account for background workflows
Environment File Layout
Docker uses per-service env files, not a root .env file.
| File | Services |
|---|---|
packages/db/.env |
migrator, seeder |
apps/app/.env |
app |
apps/portal/.env |
portal |
Each service reads only its designated env file via env_file: in docker-compose.yml.
Minimal Required Environment
For a functional Docker deployment, you need:
packages/db/.env
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
apps/app/.env
# Database
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
# Auth
AUTH_SECRET="<openssl rand -base64 32>"
SECRET_KEY="<openssl rand -base64 32>"
BETTER_AUTH_URL="https://app.yourdomain.com"
NEXT_PUBLIC_BETTER_AUTH_URL="https://app.yourdomain.com"
NEXT_PUBLIC_PORTAL_URL="https://portal.yourdomain.com"
# Email
RESEND_API_KEY="re_..."
# Workflows (required)
TRIGGER_SECRET_KEY="tr_..."
# Revalidation
REVALIDATION_SECRET="<random string>"
apps/portal/.env
# Database
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
# Auth
BETTER_AUTH_SECRET="<openssl rand -base64 32>"
BETTER_AUTH_URL="https://portal.yourdomain.com"
NEXT_PUBLIC_BETTER_AUTH_URL="https://portal.yourdomain.com"
# Email
RESEND_API_KEY="re_..."
See the Environment Reference for all variables.
Build-Time vs Runtime Variables
| Phase | Variables | How They’re Set |
|---|---|---|
| Build-time | NEXT_PUBLIC_* |
Docker build args in docker-compose.yml |
| Runtime | Everything else | Service env files (.env) |
The Dockerfile defines these build args for the app:
NEXT_PUBLIC_BETTER_AUTH_URLNEXT_PUBLIC_PORTAL_URLNEXT_PUBLIC_POSTHOG_KEYNEXT_PUBLIC_POSTHOG_HOSTNEXT_PUBLIC_IS_DUB_ENABLEDNEXT_PUBLIC_API_URL
For portal, only NEXT_PUBLIC_BETTER_AUTH_URL is used at build time.
docker-compose.yml passes build args from shell environment variables (e.g., ${BETTER_AUTH_URL}). Export these before running docker compose build, or set them inline.
Build & Run
1. Prepare Environment Files
# Copy examples to create your env files
cp packages/db/.env.example packages/db/.env
cp apps/app/.env.example apps/app/.env
cp apps/portal/.env.example apps/portal/.env
# Edit each file with your production values
2. Export Build Args
Export the build-time variables needed by docker-compose.yml:
export BETTER_AUTH_URL="https://app.yourdomain.com"
export BETTER_AUTH_URL_PORTAL="https://portal.yourdomain.com"
3. Build Images
docker compose build --no-cache
4. Run Migrations & Seed
docker compose run --rm migrator
docker compose run --rm seeder
migrator runs prisma migrate deploy — safe to run repeatedly.
seeder upserts reference data (frameworks, controls) — idempotent.
5. Start Services
docker compose up -d app portal
6. Verify Health
curl -s http://localhost:3000/api/health
curl -s http://localhost:3002/
Deploy Trigger.dev Tasks
Trigger.dev runs as a hosted service. Deploy your tasks from your workstation:
cd apps/app
bunx trigger.dev@latest login
bunx trigger.dev@latest deploy
Set TRIGGER_SECRET_KEY in apps/app/.env from your Trigger.dev project settings.
Fresh Install (Optional Clean)
To remove all images and volumes for a clean rebuild:
docker compose down --rmi all --volumes --remove-orphans
docker builder prune --all --force
Logging
Docker Compose is configured with log rotation to prevent disk exhaustion. All services use the json-file driver with these defaults:
| Setting | Value | Description |
|---|---|---|
max-size |
10m | Rotate logs at 10 MB |
max-file |
5 | Keep up to 5 rotated files (~50 MB per service) |
compress |
true | Compress rotated files |
Viewing Logs
# Tail logs for a specific service
docker compose logs -f app
# Tail multiple services
docker compose logs -f app portal
# View last 100 lines
docker compose logs --tail=100 app
# View logs for one-shot jobs
docker compose logs migrator
docker compose logs seeder
Custom Log Configuration
To adjust logging for a specific service (e.g., if app is particularly chatty), create a docker-compose.override.yml:
services:
app:
logging:
driver: json-file
options:
max-size: '50m'
max-file: '10'
compress: 'true'
For centralized logging (ELK, Loki, Splunk), you can switch the logging driver to fluentd, gelf, or loki. See Docker’s logging driver documentation.
Production Tips
HTTPS & Domains
Place a reverse proxy (nginx, Caddy, Traefik) in front. Update BETTER_AUTH_URL and NEXT_PUBLIC_BETTER_AUTH_URL to your public HTTPS domains.
Strong Secrets
Generate secrets with openssl rand -base64 32. Rotate periodically.
Database Security
Require SSL. Restrict network access via VPC, IP allowlist, or private networking.
Disk Monitoring
Monitor /var/lib/docker disk usage. Periodically prune unused containers and images.
Troubleshooting
Builds succeed but containers crash at startup
The Dockerfile sets SKIP_ENV_VALIDATION=true at build time, so missing env vars are only caught at runtime. Check logs to identify missing variables:
docker compose logs app
docker compose logs portal
Look for errors mentioning process.env.* or “Missing env var”, then compare with the Environment Reference.
Common Misconfigurations
| Issue | Solution |
|---|---|
Using root .env |
Docker reads per-service env files only |
Missing DATABASE_URL |
Must be set in all three env files |
Missing TRIGGER_SECRET_KEY |
Required in apps/app/.env for workflows |
| Mismatched auth URLs | Ensure BETTER_AUTH_URL matches NEXT_PUBLIC_BETTER_AUTH_URL |
| Build args not exported | Export BETTER_AUTH_URL and BETTER_AUTH_URL_PORTAL before build |
Container won’t start
# Check if container exists
docker compose ps -a
# View detailed logs
docker compose logs --tail=100 app
Database connection fails
- Verify
DATABASE_URLformat:postgresql://user:pass@host:5432/db?sslmode=require - Ensure your IP is allowlisted in the database firewall
- Test connectivity:
docker compose run --rm app sh -c "nc -zv <db-host> 5432"