Skip to content
BoringStack
GitHub

Environment variables

8 min read

Environment variables

Each workspace has its own .env.example for the vars it consumes. This page maps which app or infra layer owns a variable, why it exists, and which .env.example has the full reference.

4

config files

Strict

env validator

Prod-only

guards

01

apps/api

apps/api/.env. Consumed by the Bun process directly when running outside Docker.

02

apps/ui

apps/ui/.env.local. Consumed by Vite at build and dev time (VITE_* only).

03

infra (dev)

compose/.env in the infra/compose folder. Injected into the api and ui dev containers.

04

infra (prod)

compose/.env + compose/api.prod.env. Prod profile uses a separate api env file.

NODE_ENV

Repo: api. Required: yes. Values development, production, or test; gates prod-only invariants.

PORT
Repo: api. Required: no. Default 3000.
APP_NAME

Repo: api. Required: no (default API Template). Public-facing identity used in email From-names, OAuth consent metadata, and the OpenAPI document title.

LOG_LEVEL

Repo: api. Required: no. One of debug, info, warn, error.

SENTRY_DSN

Repo: api. Required: no. Empty means the SDK is a no-op.

SENTRY_TRACES_SAMPLE_RATE

Repo: api. Required: no. Range 0 to 1, default 0.1.

VITE_SENTRY_DSN

Repo: ui. Required: no. Same wire protocol as Sentry or GlitchTip.

DATABASE_URL

Repo: api. Required: yes. Postgres connection string.

DATABASE_SSL_REJECT_UNAUTHORIZED

Repo: api. Required: no. Default true. Keep it true in production so Postgres TLS is verified instead of merely encrypted.

DATABASE_SSL_CA

Repo: api. Required: no. Optional PEM CA bundle for production Postgres providers that use a private CA.

VALKEY_HOST / VALKEY_PORT

Repo: api. Required: yes when queues or cache use Valkey. Defaults localhost and 6379.

VALKEY_PASSWORD

Repo: api. Required: in production when queues, Valkey cache, notification SSE, or OAuth is on.

VALKEY_DB

Repo: api. Required: no. Default 0.

CACHE_ENABLED / CACHE_PROVIDER

Repo: api. Required: no. false uses noop; providers include memory and valkey.

QUEUES_ENABLED

Repo: api. Required: no. When false, dispatch falls back to inline execution.

NOTIFICATIONS_SSE_ENABLED

Repo: api. Required: no. Enables the live notification stream. true requires Valkey because API instances coordinate through pub/sub.

JWT_SECRET

Repo: api. Required: yes (32+ chars). Signs the 15-minute access cookie and hashes refresh tokens. Rotation forces every user to sign in again.

FRONTEND_URL

Repo: api. Required: yes. App origin used for OAuth success redirects and Stripe return-url allowlisting.

PUBLIC_API_URL

Repo: api. Required: yes. Public URL used to build OAuth callback URLs. Same-origin default is the frontend origin; cross-origin setups use the API origin.

ALLOWED_ORIGINS

Repo: api. Required: no (default empty). CSV. Empty means same-origin deployment (default); CORS is not mounted. When set, every origin must be HTTPS with no wildcards.

SUPERUSER_EMAIL / SUPERUSER_PASSWORD

Repo: api. Required: no (default empty). Optional first-boot superuser. Both set means db:seed creates an admin user on first run. Empty means no-op; register via the UI instead.

GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_CLIENT_SECRET

Repo: api. Required: when Google OAuth is enabled.

GITHUB_OAUTH_CLIENT_ID / GITHUB_OAUTH_CLIENT_SECRET

Repo: api. Required: when GitHub OAuth is enabled.

LINKEDIN_OAUTH_CLIENT_ID / LINKEDIN_OAUTH_CLIENT_SECRET

Repo: api. Required: when LinkedIn OAuth is enabled.

RATE_LIMIT_MAX / RATE_LIMIT_WINDOW_MS

Repo: api. Required: no (defaults 100 / 60000ms). Per-IP global rate limit; defence-in-depth alongside Traefik’s edge limit.

AUTH_RATE_LIMIT_MAX / AUTH_RATE_LIMIT_WINDOW_MS

Repo: api. Required: no (defaults 10 / 60000ms). Stricter per-IP limit applied to every /api/v1/auth/* route. Tightens the global default for the credential-stuffing surface.

E2E_TEST_ENDPOINTS_ENABLED

Repo: api. Required: no (default false in apps/api .env.example). When true, exposes /api/v1/auth/__test/* helpers outside NODE_ENV=test. Docker Compose dev defaults this to true via API_DEV_E2E_TEST_ENDPOINTS_ENABLED for Playwright — keep false in prod.

EMAIL_PROVIDER

Repo: api. Required: always set. One of cloudflare (default), resend, sendgrid, or smtp.

EMAIL_FROM

Repo: api. Required: always. Sender on a verified domain.

CLOUDFLARE_ACCOUNT_ID

Repo: api. Required: when provider is cloudflare.

CLOUDFLARE_EMAIL_API_TOKEN

Repo: api. Required: when provider is cloudflare. Scope Email Sending: Edit.

RESEND_API_KEY

Repo: api. Required: when provider is resend.

SENDGRID_API_KEY

Repo: api. Required: when provider is sendgrid.

SMTP_HOST

Repo: api. Required: when provider is smtp. Use mailpit against the dev overlay; any RFC 5321 server in prod.

SMTP_PORT

Repo: api. Required: when provider is smtp. Default 25; Mailpit listens on 1025.

SMTP_USER / SMTP_PASS

Repo: api. Required: optional. Auth when the server requires it.

NOTIFICATION_SETTINGS_URL

Repo: api. Required: no. Optional http(s) link rendered in notification email footers.

ACCOUNT_DOMAIN_CLAIMING

Repo: api. Required: no (defaults false). When true, the first verified signup with a non-public email domain claims that domain on its personal account; subsequent signups from the same domain hit DOMAIN_CLAIMED (409) and must be invited through the standard invitations flow. The public-email allowlist (gmail.com, outlook.com, etc.) is hard-coded. Those domains always get fresh personal accounts. Leave off for consumer products. See Multi-tenant model → Domain claiming.

BILLING_ENABLED

Repo: api. Required: no. When true, Stripe keys are required.

STRIPE_SECRET_KEY

Repo: api. Required: when billing is on.

STRIPE_WEBHOOK_SECRET

Repo: api. Required: when billing is on.

STRIPE_PRICE_ID_FREE / STRIPE_PRICE_ID_PRO

Repo: api. Required: when billing is on. Used to upsert the template’s Free and Pro plans at runtime.

AI_ENABLED

Repo: api. Required: no.

AI_PROVIDER

Repo: api. Required: when AI is on. One of openai, anthropic, or noop.

OPENAI_API_KEY

Repo: api. Required: when AI provider is openai.

OPENAI_BASE_URL

Repo: api. Required: no. Point at OpenAI-compatible APIs (OpenRouter, Ollama, vLLM).

OPENAI_DEFAULT_HEADERS

Repo: api. Required: no. Optional JSON object of extra headers passed on every OpenAI client request. Useful for OpenRouter ranking (HTTP-Referer, X-Title) or for custom auth on a compatible endpoint. Empty means no extra headers.

ANTHROPIC_API_KEY

Repo: api. Required: when AI provider is anthropic.

VITE_API_URL

Repo: ui. Required: no (default empty). API base URL. Empty means same-origin (/api/* relative). Set a full HTTPS URL only for cross-origin deployments.

VITE_LOCALES

Repo: ui. Required: no. CSV of locale codes; first entry is the fallback.

VITE_PUBLIC_URL

Repo: ui. Required: yes (default http://localhost:3001). Public SPA origin used for billing return URLs, SEO, and share links. In same-origin deploys this matches the browser origin; cross-origin setups still need the UI’s public URL here (not the API host).

VITE_VAPID_PUBLIC_KEY

Repo: ui. Required: no (default empty). Web Push public key. Must be the same value as API WEB_PUSH_VAPID_PUBLIC — generate both sides together with bun run vapid:generate in apps/api. Empty hides the subscribe UI; the API also exposes capabilities.features.notifications.webPush.

STACK

Repo: infra. Required: yes. dev or prod.

POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB

Repo: infra. Required: yes. Postgres bootstrap credentials.

PUBLIC_UI_HOST

Repo: infra. Required: yes in prod. Apex DNS name for Traefik. Same-origin path routing serves both UI and /api/* from this single host.

ACME_EMAIL

Repo: infra. Required: yes in prod. Let’s Encrypt contact address.

GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD

Repo: infra. Required: when observability overlay is on.

*_LIMITS_CPUS / *_LIMITS_MEMORY (and reservations)

Repo: infra. Required: no. Per-service resource caps; see Resource limits.

WITH_OBSERVABILITY / WITH_GLITCHTIP / WITH_BULLMQ / WITH_WUD

Repo: infra. Required: no. Overlay toggles.

RCLONE_REMOTE_NAME / RCLONE_REMOTE_PATH

Repo: infra. Required: when backups are configured.

BACKUP_RETENTION_DAYS

Repo: infra. Required: no. Default 30.

BACKUP_DRY_RUN

Repo: infra. Required: no. 1 means the script logs but does not act.

The sections above are an index. For defaults, comments, and per-var reasoning:

  • Env validator; how the apps/api enforces required + cross-field rules.
  • Secrets; handling the sensitive subset.