Environment variables
This page is the complete list. For the must-set subset see
Required environment variables;
for integration keys see
Optional integration keys.
The annotated source of truth is
.env.example.
Ownership model
Section titled “Ownership model”Two layers:
- App-level (
apps/*/src/config/env.ts) — vars the app itself cares about (bind port, DB URL, frontend origin, session signing). Each app validates with zod at boot. - Package-level (
packages/infra/<pkg>/src/config.ts) — vars that belong to a package (@scani/securityownsENCRYPTION_KEY,@scani/storageownsS3_*,@scani/emailownsFASTMAIL_API_TOKEN/SMTP_URL, …).
Apps that depend on a package do not redeclare the package’s
vars. The package’s loadXConfig() validates and caches; the app
just sets the env var.
See Engineering conventions for the ownership rule.
Core (required for any deployment)
Section titled “Core (required for any deployment)”Tier wiring
Section titled “Tier wiring”Admin dashboard
Section titled “Admin dashboard”The passkey-gated infra console (apps/frontend/admin, Next.js). None
of these are needed to run Scani — the admin app is an operator tool and
a self-host deployment can skip it entirely.
Storage
Section titled “Storage”Logging
Section titled “Logging”Provider keys (read by the data-provider)
Section titled “Provider keys (read by the data-provider)”In Tier 1 these live on your data-provider; in Tier 2/3 on the hosted data-provider.
Observability
Section titled “Observability”API port shape across deployment layers
Section titled “API port shape across deployment layers”The number 3001 shows up in three places that mean different things;
trying to “fix” any one of them in isolation tends to break the other
two:
VITE_API_URL follows the same split: http://localhost:3001 in
host-dev, http://localhost:3011 in dev compose (frontend container’s
own env), /api baked into the prod frontend-app image so nginx
handles routing.
Health-check endpoints
Section titled “Health-check endpoints”All exposed by apps/backend/api (and surfaced via nginx as
/api/* in prod compose):
The data-provider exposes /health (process liveness) on its bind
port. The prod frontend-app image exposes /healthz (nginx alive),
not to be confused with /api/health/* (which goes through to the
api).
Testing-only
Section titled “Testing-only”These vars are read only by the e2e test runner under apps/e2e/
and the related fixtures / scripts. They have no effect on a
production deployment — operators can ignore this section.
Validation pattern
Section titled “Validation pattern”Every loader uses zod and the helpers from @scani/config:
isProduction—process.env.NODE_ENV === 'production'at load.urlSchema/httpsUrlInProduction— URL with prod-only https requirement.requiredInProd(schema, name)— returns the schema unchanged in prod,.optional()everywhere else. Lets dev/test boot without the var; prod refuses to start without it.
On a parse failure, the loader throws with a message listing every failing variable:
@scani/security env misconfigured: - ENCRYPTION_KEY: ENCRYPTION_KEY required in productionAdding a new env var
Section titled “Adding a new env var”- Where does it belong? If it’s about a package’s behaviour
(a new third-party API key, a logging knob), it goes in that
package’s
src/config.ts. If it’s about an app (a new bind address), it goes inapps/*/src/config/env.ts. - Add it to root
.env.examplewith an annotation. - Add it to the relevant app’s
.env.example(soscripts/sync-env.tspropagates it to the per-app.env). - Validate it in the right loader.
- Document it on this page and on Required env or Optional keys.
See also
Section titled “See also”- Required environment variables
- Optional integration keys
- Engineering conventions — env-var ownership rule.
.env.examplein the repo root for the canonical comments.