Configuration
Assign every product setting to the correct runtime boundary without exposing secrets.
Configuration
Configuration is owned by the layer that consumes it. The server validates private and deployment
values once; the browser receives only intentionally public VITE_* values; deployment systems own
their secret stores; provider dashboards own provider resources. Do not turn a local .env file
into a second configuration authority.
The exact variables and defaults live in apps/server/.env.example and apps/web/.env.example.
They are executable examples, while this page explains when a group is required and where it
belongs.
Configuration map
| Concern | Canonical owner | Required when | Key examples |
|---|---|---|---|
| Database and runtime | Server environment validated in apps/server/src/config/env.ts | Always | DATABASE_URL, pool limits, ENVIRONMENT, PORT |
| Identity and browser origin | Server environment and browser public environment | Always outside local development | BETTER_AUTH_SECRET, FRONTEND_URL, SERVER_URL, VITE_API_URL, TRUSTED_PROXY_PROFILE |
| Deployment identity | Deployment environment and immutable release artifact | A deployed API exists | DEPLOYMENT_CLOUD, DEPLOYMENT_ENVIRONMENT, RELEASE_ID, IMAGE_DIGEST |
| Transactional email | Server environment and Resend account | Production email is sent | RESEND_API_KEY, EMAIL_FROM, EMAIL_TEMPLATES_DIR |
| Billing | Server environment and exactly one provider dashboard | Paid access is enabled | BILLING_PROVIDER, selected provider credential, webhook secret, product IDs, display prices |
| Object storage | Server environment and selected storage account | Uploads are enabled | STORAGE_PROVIDER, bucket values, R2/S3/GCS credentials |
| OAuth | Server environment and provider dashboard | Google sign-in is enabled | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, VITE_GOOGLE_CLIENT_ID |
| Observability and alerts | Server or browser environment and selected service | Sentry or Slack is enabled | SENTRY_PROJECT_IDS, browser VITE_SENTRY, build-only Sentry upload values, SLACK_BOT_TOKEN |
| Product identity and policies | Source-owned browser configuration and public environment | Before real users | BRAND_NAME, VITE_TERMS_URL, VITE_PRIVACY_URL |
| Local-only demo sign-in | Local web environment and a local seeded account | Development convenience only | VITE_DEV_LOGIN_EMAIL, VITE_DEV_LOGIN_PASSWORD |
VITE_* values are compiled into browser assets. They must never contain server credentials,
webhook secrets, database URLs, private origins, or provider API keys.
Environment by purpose
| Environment | Purpose | Required behavior |
|---|---|---|
| Local development | Build product behavior without third-party accounts | Postgres and Better Auth work. Email uses the local log; billing, storage, Slack, Sentry, and Google remain visibly disabled unless configured completely. |
| Test | Prove isolated behavior in CI or a disposable database | Test-only values, no external delivery, no shared database, and no production credentials. |
| Staging | Rehearse the exact deployment and provider boundary | Separate origins, database, secret store, and provider test/sandbox resources from production. |
| Production | Accept real users and durable data | Exact HTTPS origins, unique secret, selected proxy profile, verified sender, backup/restore proof, and complete live configuration for every enabled provider. |
Do not point a preview deployment at the production API. Use a stable staging browser/API pair under the same registrable site when testing cookies.
Configure in dependency order
- Copy the two example environment files to ignored local files. Set only Postgres, the local Better Auth secret, and local origins first.
- Run the zero-key path from Setup. A local account, verification, reset, and magic link must work before adding provider credentials.
- Choose the production topology and set exact origins, proxy profile, deployment identity, and secret ownership as described in Deployment.
- Enable one optional capability at a time. Supply every value in its group, start the server, and run its failure and success smoke before enabling the next group.
- Record the variable owner, rotation owner, source dashboard, and last verified date in the deployment's private operations record. Record names and owners, never values, in tickets or release notes.
The add an environment variable path is required when you change the application configuration contract. It covers validation, examples, generation, deployment, documentation, and verification together.
Optional capability groups
| Capability | All-or-nothing contract | Where to continue |
|---|---|---|
| Billing | Select polar, stripe, or dodo; provide that provider's credential, webhook secret, and at least one monthly or yearly product mapping. Leave the selected provider's integration values all unset for zero-key local work. | Billing |
| Storage | Select one provider and provide the complete bucket, public URL, region, and matching credentials required by that provider. | Object storage |
| Provide server client ID/secret, browser client ID, exact provider callback, and allowed origins together. | Authentication | |
| Sentry | Provide the public browser DSN and server allowlist together; build upload credentials belong only in CI or the protected build environment. | Security |
| Slack | Provide the server token only when sanitized operational alerts have a defined owner. Its absence is a valid disabled state. | Notifications and marketing endpoints |
Changing BILLING_PROVIDER after customers exist is not ordinary configuration. It requires an
explicit entitlement migration and reconciliation plan; one deployment must not combine provider
entitlements.
Safe configuration checks
The server fails at startup for invalid required values and partial selected billing or storage configuration. Validate the intended environment without printing secrets:
vp run --filter @app/server typecheck
vp run --filter @app/server test:unit
vp run --filter @app/server buildThen exercise the selected deployment's /ready endpoint and the enabled provider's real test or
live smoke. Use Troubleshooting for the first observable failure instead of
loosening origin, cookie, validation, or signature policy.
Values that must stay out of source control
Never commit real .env files, backend.hcl, .tfvars, state, cloud credentials, database URLs,
webhook secrets, Better Auth secrets, OAuth secrets, provider API keys, signed URLs, or customer
data. A name or public origin can be documented; a credential value cannot.