Skip to content

Required environment variables

These are the variables every Scani deployment must set. Optional integration keys live on the next page; the full annotated list lives in .env.example and is enumerated in the Environment variables reference.

VariableWhat it doesHow to generate
NODE_ENVproduction for any real deployment.Set to production.
DATABASE_URLPostgres 16+ connection string. SSL mode required for non-localhost.Provided by your Postgres host.
REDIS_URLRedis 7+ connection string. Powers BullMQ, the rate-limiter, and pub/sub.Provided by your Redis host.
POSTGRES_POOL_MAXPer-app pool size. Default 20 for direct endpoints; set to 5 if your URL routes through PgBouncer or another connection pooler.Set explicitly when using a pooler.
VariableWhat it doesHow to generate
BETTER_AUTH_SECRET32+ chars. Better-Auth session signing key. Rotating it invalidates every active session.openssl rand -hex 32
ENCRYPTION_KEYAt least 32 chars; recommended 64 hex chars. AES-256-GCM key that encrypts integration credentials at rest. A 64-hex-char value is used directly as the AES key (no KDF); any other ≥32-char string is run through scrypt. Must match between api and worker — if they differ, the worker cannot decrypt what the api stored and every import silently fails.openssl rand -hex 32
JOBS_HMAC_SECRET32+ chars. Shared secret for HMAC-gated operator tooling (BullMQ retry/remove, DLQ replay).openssl rand -hex 32
SCREENSHOT_BOT_SECRET32+ chars. Bearer for the /api/auth/screenshot-bot/sign-in endpoint, which mints a Better-Auth session for the single allow-listed screenshot-capture user. Optional everywhere — when unset the endpoint refuses with 403, disabling the feature without blocking boot. Set it if you use a screenshot-capture pipeline.openssl rand -hex 32
LOG_ID_PEPPER16+ chars. Pepper used to one-way hash user / tenant / account IDs in structured logs. Production boot fails without it.openssl rand -hex 32
VariableWhat it does
FRONTEND_URLThe browser-facing URL of the SPA (e.g. https://scani.example.com). Used for CORS and the Better-Auth cookie scope. Must be https:// in production.
BACKEND_URLThe browser-facing URL of the api (e.g. https://api.scani.example.com or https://scani.example.com/api). Embedded in magic-link emails — must be reachable by the user’s email client.
COOKIE_DOMAINSet to .your-domain.example.com if api and SPA live on different subdomains. The session cookie is set with this domain so it reaches both. Leave unset if both share an origin.
VariableWhat it does
SCANI_CLOUD_URLWhere the api + worker send outbound third-party calls. Tier 1: http://data-provider:8082 (the same compose network). Tier 2/3: a hosted data-provider endpoint.
SCANI_CLOUD_API_KEYBearer the api + worker present to the data-provider. In Tier 1, matches DATA_PROVIDER_API_KEY. In Tier 2/3, issued by the operator.
DATA_PROVIDER_API_KEYWhat the data-provider validates incoming bearers against. In Tier 1, equals SCANI_CLOUD_API_KEY. In Tier 2/3, lives on the hosted data-provider, not on your side.
VariableWhat it does
S3_ENDPOINTS3-compatible endpoint URL. Tier 1 in compose: http://minio:9000. Cloud providers: their endpoint.
S3_PUBLIC_ENDPOINTWhat gets baked into presigned URLs the browser uses. Often the same as S3_ENDPOINT but differs for compose-internal MinIO (http://localhost:9000 for the browser).
S3_ACCESS_KEY_IDProvider-issued.
S3_SECRET_ACCESS_KEYProvider-issued.
S3_BUCKETBucket name. Must exist; the minio-init service creates it for compose-managed MinIO.

Pick one. Booting with neither configured fails on the first email send.

VariableWhat it does
FASTMAIL_API_TOKENFastmail JMAP token. Takes precedence when set.
SMTP_URLsmtp://user:pass@host:port for any SMTP server.
SMTP_FROMThe from address for outbound mail.
VariableWhat it does
LOG_LEVELdebug, info, warn, error. Default info in production.
LOG_PRETTYPretty-print logs. Default false in production.
LOG_SQL_QUERIESDefault false. Turn on briefly for debugging.

Before booting production:

Terminal window
# Read back every required-in-prod var
docker compose -f docker-compose.prod.yml config | grep -E '^(\s+)?(DATABASE_URL|REDIS_URL|BETTER_AUTH_SECRET|ENCRYPTION_KEY|JOBS_HMAC_SECRET|FRONTEND_URL|BACKEND_URL|SCANI_CLOUD_API_KEY|DATA_PROVIDER_API_KEY|LOG_ID_PEPPER):'
# A missing required var fails with a readable message
docker compose -f docker-compose.prod.yml up -d