Environment variables
Config file locations
Section titled “Config file locations”The stack reads three sets of environment files with this precedence order:
- apps/api/.env: consumed by the API process when running outside Docker
- compose/.env: injected into dev containers via
env_file:references - compose/api.prod.env: API-specific production overrides
In Docker Compose (the default), compose/.env is the single file you edit most often. It feeds into both app containers and the infra services (Postgres, Valkey, Traefik, observability).
Per-app usage
Section titled “Per-app usage”| Location | Consumed by | Format |
|---|---|---|
| apps/api/.env | API Bun process (outside Docker) | All vars |
| apps/ui/.env.local | Vite at build/dev time | VITE_* only |
| compose/.env | all services via docker-compose.yml | see docker-compose.yml env_file refs |
| compose/api.prod.env | API service in prod | API vars + STACK=prod-specific |
Critical variables (operator attention)
Section titled “Critical variables (operator attention)”These deserve special handling because they affect security, availability, or startup:
JWT_SECRET: Signs access cookies and refresh tokens. Rotation forces every user to sign in again. Generate with openssl rand -base64 48.
MFA_ENCRYPTION_KEY: AES-256-GCM key for TOTP secrets. Once users enrol, losing this key means every MFA user re-enrols from scratch. Generate with openssl rand -base64 32 and back up to hardware or paper.
POSTGRES_PASSWORD / VALKEY_PASSWORD: Data plane secrets. Rotate in production by updating the var, then restarting services. Database passwords should be 32+ random bytes. VALKEY_PASSWORD is required in production — the Valkey server itself enforces it via --requirepass, and the API’s env validator refuses to boot without it whenever queues, the Valkey cache, SSE notifications, or OAuth are enabled. Leaving it empty (the dev default) disables Valkey auth for local convenience only.
DATABASE_URL: Postgres connection string. Must include TLS mode for production (?ssl=require). In development defaults to localhost; in production, points to a managed provider or socket.
PUBLIC_UI_HOST: Apex domain name for production routing via Traefik. Traefik uses this to request Let’s Encrypt certificates. Same-origin deployments route /api/* and all other paths from one domain.
EMAIL_PROVIDER + EMAIL_FROM: Sender identity and service. Cloudflare is the default; Resend and SendGrid are drop-in alternatives. Set both to avoid bounce errors.
Where the full list lives
Section titled “Where the full list lives”For defaults, type hints, and complete enumeration:
- apps/api:
.env.example - apps/ui:
.env.example - infra:
compose/.env.example
The .env.example files define every variable and its default. When you need to understand a variable’s default or type, start there.
Optional features (toggle by setting vars)
Section titled “Optional features (toggle by setting vars)”- Billing: set
BILLING_ENABLED=true+ Stripe keys - OAuth: set
GOOGLE_OAUTH_CLIENT_ID/CLIENT_SECRETor GitHub/LinkedIn equivalents - Observability: set
WITH_OBSERVABILITY=0to opt out (on by default) - Error tracking: set
WITH_GLITCHTIP=0to opt out (on by default) - Queue dashboard: set
WITH_BULLMQ=1(dev only, no auth) - Email catcher: set
WITH_MAILPIT=1(dev only)
Related
Section titled “Related”- Commands cheatsheet; which scripts to run for setup and day-to-day work
- Profiles & overlays; how STACK= and WITH_* flags compose services
- Deployment; production secrets into 1Password and how to inject them