Skip to main content

Environment Variables

Required

VariablePurpose
DATABASE_URLPostgres connection string. Pooled URL for serverless.
NEXTAUTH_SECRET32-byte random secret for JWT signing. openssl rand -base64 32.
NEXTAUTH_URLPublic origin (https://cms.example.com).
ADMIN_EMAIL, ADMIN_PASSWORDBootstrap admin user. Used by pnpm db:seed.
SUPABASE_URLSupabase project URL.
SUPABASE_SERVICE_KEYServer-side service role key.
SUPABASE_STORAGE_BUCKETBucket name. Auto-created (public) on first upload if it doesn't exist.
VariablePurpose
CRON_SECRETBearer token for /api/cron/* handlers. Required in production.
CRON_SCHEDULED_PUBLISH_INTERVAL_MINSubsample the hourly scheduled-publish cron — only run work every N minutes so Neon can scale to zero between ticks. Recommended 15.
CRON_WEBHOOK_RETRY_INTERVAL_MINSame subsampling for the webhook-retry cron. Recommended 15.
KRIOS_DATA_REGIONInformational region label surfaced via X-Krios-Data-Region header.
DATABASE_URL_UNPOOLEDReserved / not currently wired — the Prisma schema declares no directUrl, so migrations run against DATABASE_URL. Keep it set for forward-compatibility, but it isn't read today.

Optional

VariablePurpose
KRIOS_DATABASE_REGIONDisplay-only, e.g. Neon (us-east-2) for Settings → Security.
KRIOS_STORAGE_REGIONDisplay-only, e.g. Supabase (us-east-1).
STORAGE_PROVIDERsupabase (default) or local.
SEARCH_PROVIDERpostgres (default) or meilisearch.
MEILI_HOSTMeilisearch endpoint (when SEARCH_PROVIDER=meilisearch). The functional name read by the search provider.
MEILI_API_KEYMeilisearch admin key. The functional name read by the search provider.
KRIOS_TENANT_DB_KEY32+ char key for AES-256-GCM encryption of per-tenant DB URLs.
CORS_ALLOWED_ORIGINSComma-separated global CORS allow-list fallback (used when a project/site has no specific config).
RATE_LIMIT_DELIVERYOverride the project-level delivery rate limit.
RATE_LIMIT_MANAGEMENTOverride the project-level management rate limit.
Meilisearch variable names

The codebase is inconsistent here: the search provider reads MEILI_HOST / MEILI_API_KEY (the functional pair, above), while .env.example and the /api/health probe read MEILISEARCH_URL / MEILISEARCH_API_KEY. Set MEILI_HOST / MEILI_API_KEY to make search work; also set the MEILISEARCH_* pair if you want the health check to report Meilisearch correctly.

Other optional vars: MAX_FILE_SIZE_MB (upload size cap), LOG_LEVEL (structured-log verbosity), and Sentry (SENTRY_DSN, NEXT_PUBLIC_SENTRY_DSN). WEBHOOK_DEFAULT_TIMEOUT_MS is not read by code — the webhook delivery timeout is a hardcoded 5s and is not configurable.

Local dev .env

DATABASE_URL="postgresql://..."
NEXTAUTH_SECRET="$(openssl rand -base64 32)"
NEXTAUTH_URL="http://localhost:3000"
ADMIN_EMAIL="you@example.com"
ADMIN_PASSWORD="…"
SUPABASE_URL="https://….supabase.co"
SUPABASE_SERVICE_KEY="…"
SUPABASE_STORAGE_BUCKET="krios-media"

Production extras

CRON_SECRET="$(openssl rand -base64 32)"
CRON_SCHEDULED_PUBLISH_INTERVAL_MIN="15" # subsample the hourly cron so Neon can idle
CRON_WEBHOOK_RETRY_INTERVAL_MIN="15"
KRIOS_DATA_REGION="us-east"
KRIOS_DATABASE_REGION="Neon (us-east-2)"
KRIOS_STORAGE_REGION="Supabase (us-east-1)"
KRIOS_TENANT_DB_KEY="<32+ char random>" # only when using V3 physical isolation
SEARCH_PROVIDER="meilisearch" # only when using Meilisearch
MEILI_HOST="https://meili.example.com"
MEILI_API_KEY="…"

Loading in Vercel

Settings → Environment Variables. Set each variable for Production, Preview, and Development as appropriate. The same variable can have different values per environment.

Sensitive values (*_SECRET, *_KEY, *_PASSWORD) should be marked as encrypted in the Vercel UI — Vercel encrypts them at rest and surfaces them only to the running function.

Loading in self-hosted

Source from your secrets manager into the process environment before pnpm start. Don't commit .env.production to source control. systemd services should use EnvironmentFile= pointing at a root-only file, or pull from Vault / AWS Secrets Manager / Doppler at boot.