Environments
Subdomain topology for development, staging, and production, and which variables each runtime needs.
Topology
Every app has a stable role key in code and a DNS label that depends on the environment. Staging
uses the fixed cenvora.dev base; production uses ROOT_DOMAIN, and development uses localhost.
The mapping lives in packages/env/src/hosts.ts and is the only place it is defined; apps call
hosts(), originFor(), or read publicHosts / serverHosts rather than building URLs by hand.
| Role | Production | Staging | Development |
|---|---|---|---|
marketing | https://www.lee-kong-si.cenvora.dev | https://www.lee-kong-si-test.cenvora.dev | http://localhost:3004 |
app | https://app.lee-kong-si.cenvora.dev | https://app.lee-kong-si-test.cenvora.dev | http://localhost:3000 |
docs | https://docs.lee-kong-si.cenvora.dev | https://docs.lee-kong-si-test.cenvora.dev | http://localhost:3001 |
platform | https://platform.lee-kong-si.cenvora.dev | https://platform.lee-kong-si-test.cenvora.dev | http://localhost:3002 |
partner | https://partner.lee-kong-si.cenvora.dev | https://partner.lee-kong-si-test.cenvora.dev | http://localhost:3003 |
api | https://api.lee-kong-si.cenvora.dev | https://api.lee-kong-si-test.cenvora.dev | http://localhost:4000 |
Production uses APP_ENV=production, PROJECT_NAME=lee-kong-si, and
ROOT_DOMAIN=cenvora.dev; this deliberately resolves to the six project-scoped Cenvora hosts
shown above. Staging uses APP_ENV=staging, PROJECT_NAME=lee-kong-si-test, and the same
fixed cenvora.dev base. The six current purpose labels are www, app, docs, platform,
partner, and api. A custom non-Cenvora production root uses admin for the platform host;
the project-scoped Cenvora convention uses platform in both environments.
Future approved purposes such as merchant or user are not current apps or HostRole values.
With PROJECT_NAME=lee-kong-si-test and ROOT_DOMAIN=cenvora.dev, staging yields
app.lee-kong-si-test.cenvora.dev. The production Cenvora convention similarly yields
app.lee-kong-si.cenvora.dev; a separately owned production registrable domain can still be
provided through ROOT_DOMAIN when approved. The staging base is fixed to cenvora.dev.
Print the resolved table at any time with:
bun run vigor hosts --env staging
bun run vigor hosts --env production --jsonThe session cookie domain is .<project>.cenvora.dev in staging,
.<ROOT_DOMAIN> in production, and unset on localhost. This lets one login work across the
sibling services of the same project. See Authentication.
Other staging projects do not receive that project-domain cookie. This narrower cookie scope
is not full security isolation: all projects remain same-site under the shared registrable
cenvora.dev domain, and cookie-tossing risks remain. Trust every sibling service within one
project, and place mutually untrusted projects on separate registrable domains.
Environment variables
The root .env.example lists every variable. Server-side variables are validated by
@vigor/env/server (serverEnv); browser-safe variables carry the NEXT_PUBLIC_ prefix and
are validated by @vigor/env/next (nextEnv). The API requires its server configuration at
boot. Next.js site builds only need their browser-safe topology and integration variables.
Topology
| Variable | Runtime | Notes |
|---|---|---|
APP_ENV / NEXT_PUBLIC_APP_ENV | API, CLI / Next.js | development, staging, production. |
PROJECT_NAME / NEXT_PUBLIC_PROJECT_NAME | API, CLI / Next.js | Project DNS label and staging cookie scope. |
ROOT_DOMAIN / NEXT_PUBLIC_ROOT_DOMAIN | API, CLI / Next.js | Production and canonical registrable-domain input, including in staging builds. Use localhost in development and cenvora.dev in staging for clarity; this input does not change the fixed staging placement. |
PORT | API | Defaults to 4000. |
Data and auth
| Variable | Runtime | Notes |
|---|---|---|
DATABASE_URL | API, CLI | PostgreSQL connection string. Required. |
BETTER_AUTH_SECRET | API | Minimum 32 characters. Intentionally empty in .env.example; generate a unique value with openssl rand -base64 32. |
BETTER_AUTH_URL | API | Optional auth-base override; defaults to the API origin for APP_ENV. During a staging cutover, unset it or update it to https://api.<project>.cenvora.dev; a stale override keeps using the old API host. |
| Variable | Runtime | Notes |
|---|---|---|
RESEND_API_KEY | API | May be empty only in explicit development, where email is logged. Required in test, staging, and production. |
EMAIL_FROM | API | Display name and address, for example Acme <no-reply@example.com>. |
Error logs
| Variable | Runtime | Notes |
|---|---|---|
ROOTPRINT_URL | API and Next.js server runtimes | Rootprint base URL. Server only. |
ROOTPRINT_INGEST_KEY | API and Next.js server runtimes | Secret ingest key scoped to otel-logs-v0_9. Server only. |
NEXT_PUBLIC_ROOTPRINT_ENABLED | Next.js browser | Exact true opts into sanitized same-origin /monitoring forwarding; false is the default. |
ROOTPRINT_URL and ROOTPRINT_INGEST_KEY are a pair: leaving both blank disables server
delivery; setting only one is invalid. Browser code never receives the ingest key.
Feature flags
| Variable | Runtime | Notes |
|---|---|---|
GROWTHBOOK_API_HOST / NEXT_PUBLIC_GROWTHBOOK_API_HOST | API / Next.js | GrowthBook API host. |
GROWTHBOOK_CLIENT_KEY / NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY | API / Next.js | SDK client key. When set, GrowthBook wins over FLAGS_JSON. |
FLAGS_JSON / NEXT_PUBLIC_FLAGS_JSON | API / Next.js | JSON object of flag values used when no client key is set. |
AI and client communication
| Variable | Runtime | Notes |
|---|---|---|
OPENROUTER_API_KEY | API | Server only. Apps reach models through POST /api/ai/chat. |
OPENROUTER_MODEL_FAST, OPENROUTER_MODEL_BALANCED, OPENROUTER_MODEL_STRONG | API | Optional overrides for the model tiers. |
AI_DAILY_REQUEST_LIMIT | API | Chat requests each user may make per UTC day (default 100). Further requests get 429. |
AUDIT_RETENTION_DAYS | API | Days to keep audit log entries (default 365); the daily cleanup job deletes older ones. |
TRUSTED_PROXIES | API | Proxy addresses whose X-Forwarded-For is trusted (default loopback,linklocal,uniquelocal). Used for rate limits and session IPs. |
WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_CLIENT_RECIPIENTS | API, CI | Optional; disabled when empty. |
Misc
| Variable | Runtime | Notes |
|---|---|---|
MCP_ENABLED | API | Defaults to true in development and false elsewhere. Set it to override. |
MCP_TOKEN | API | Bearer token (32+ characters) required for /mcp outside development. The API fails at startup if MCP is enabled without it. |
APP_VERSION | Any | Optional release override reported with Rootprint error logs. |
Per-environment checklist
- Development: copy
.env.exampleto.env, generateBETTER_AUTH_SECRET, keep both Rootprint server values blank, and keepNEXT_PUBLIC_ROOTPRINT_ENABLED=false. - Staging: set
APP_ENV=stagingandNEXT_PUBLIC_APP_ENV=staging, matchPROJECT_NAMEandNEXT_PUBLIC_PROJECT_NAME, and usecenvora.devfor both root-domain variables unless retaining a production domain for canonical metadata. Host generation always usescenvora.devfor staging. Use a uniqueBETTER_AUTH_SECRETand, when approved, the staging Rootprint URL/key pair and GrowthBook client key. Browser reporting remains opt-in. - Production: set both app-environment variables to
productionand both root-domain variables to the project's production domain. Generate different auth and ingest secrets; staging and production must never share them or their data stores.