Deployment configuration is entirely environment variables. There is no configuration file format and no CLI — the surface an operator controls is the process environment. Tenant configuration is a different thing and lives in the database, reached over the API: scanner selection per target kind (/api/v1/settings/scanners), scan schedules, policies, webhook endpoints, registry credentials and entitlement overrides. The distinction is worth holding onto — an operator sets what the deployment can do, and a tenant administrator sets what their organisation does within it. .env.example in the repository root is the canonical list. If a setting is not there, it does not exist; if it is there, backend/internal/config reads it. Keeping one source of truth is what prevents drift: the API port was once simultaneously 8080 in .env.example, 4000 in the compose file and 4000 in the Dockerfile, while every OAuth redirect URI assumed 8080.

Precedence

.env is gitignored and must never be committed. A missing .env is normal in a deployed environment, so its absence is not an error.

The two rules

There is no such thing as a default secret

A shipped default value for a signing key is a published key: anyone who reads the repository can forge credentials with it. TRUSTIVAN had exactly this problem, and it was a documented security finding rather than a hypothetical. So a secret is either supplied by the operator, generated randomly per process in development, or the process refuses to start. There is no fourth option.
Development stays frictionless — there is no setup step — while making it impossible for a fixed secret to leak into a deployment, because none exists. The cost is that sessions do not survive a restart in development, which is the correct thing to trade.

Configuration fails closed

ENV is a security control, not a label. Anything that is not development — an unset ENV and ENV=test included — is treated as a deployed environment and validated strictly, so ENV=prodction, or no ENV at all, fails safe rather than open. Missing required configuration is a fatal startup error, never a warning that scrolls past in a log. A security control that a missing environment variable can silently disable is not a control. The same reasoning appears throughout:
  • WEBHOOK_ALLOW_PRIVATE_DESTINATIONS=true outside development is refused at startup, because a deployment that can deliver to its own loopback interface can be pointed at its own instance metadata service by anyone who can create an endpoint.
  • VULN_DB_PROVIDER=mirror with no repositories configured is a startup error rather than a quiet fall back to the public internet, which would defeat the air gap the operator was configuring.
  • A vulnerability database past its hard age limit fails the scan rather than reporting a confidently empty result.

Derived keys, not more settings

TOKEN_ENCRYPTION_KEY is a master key. The signing key for pagination cursors, the webhook signing secret and the salt for secret findings are all derived from it rather than configured separately. This is a deliberate reduction in the number of things a deployment can forget. A separately configured cursor-signing key that nobody set would fall back to an empty secret, making every cursor forgeable — and a forged cursor is a tenant boundary written in JSON. A derived key cannot be unset while the master key is set. For the same reason there is no environment variable that mints or seeds an API key. Keys are created through the API and their secrets returned exactly once: a credential in a config file is a credential in a backup, a shell history and a container image layer.

Where the settings are documented

Deployment-shaped settings — metrics, replicas, health probes — are covered in Deployment topology and Scaling.

Adding a setting

If you are working on TRUSTIVAN rather than deploying it, the discipline is: add the field to Config, read it in Load(), add validation to resolveSecrets() with no default if it is a secret, and document it in .env.example with why it exists. A setting that exists in code but not in .env.example is invisible to whoever deploys the product.