Skip to content
BoringStack
Star

Environment variables

3 min read

The stack reads three sets of environment files with this precedence order:

  1. apps/api/.env: consumed by the API process when running outside Docker
  2. compose/.env: injected into dev containers via env_file: references
  3. 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).

LocationConsumed byFormat
apps/api/.envAPI Bun process (outside Docker)All vars
apps/ui/.env.localVite at build/dev timeVITE_* only
compose/.envall services via docker-compose.ymlsee docker-compose.yml env_file refs
compose/api.prod.envAPI service in prodAPI vars + STACK=prod-specific

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.

For defaults, type hints, and complete enumeration:

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_SECRET or GitHub/LinkedIn equivalents
  • Observability: set WITH_OBSERVABILITY=0 to opt out (on by default)
  • Error tracking: set WITH_GLITCHTIP=0 to opt out (on by default)
  • Queue dashboard: set WITH_BULLMQ=1 (dev only, no auth)
  • Email catcher: set WITH_MAILPIT=1 (dev only)