Skip to main content

Configuration

Vidra's configuration has two layers with a rule between them, and the rule is what makes the split predictable:

A value that cannot be changed safely while the process is running is environment-only. Everything else lives in the runtime settings registry and is editable from the admin console.

Secrets are always environment-only.

Layer 1: the environment file

config.Load reads the environment into one strongly validated structure and validate() fails fast — in production it hard-refuses dev secrets, dev mail capture, a plain-HTTP origin without explicit consent, and a range of other unsafe combinations. The api does not boot on a bad config; it does not start degraded.

env/production.env is generated by vidra setup from env/production.env.example. That template is the authoritative, commented list of every environment key, and it is where you should read them:

This page deliberately does not reproduce that list. A partial copy of a generated template is a copy that drifts, and a config key documented wrongly is worse than one not documented at all.

The keys most likely to bite are covered where they matter:

AreaPage
Storage, S3 and IPFSStorage backends
TLS topology and the edgeProduction deployment
Search wiring and the shared HMACSearch and discovery
Federation switches and key-encryption keysFederation
Transcoding, packaging and codecsThe video pipeline
Compose profilesDocker services

Validate a file without deploying it:

vidra setup --check env/production.env
./deploy/compose.sh config -q # render check — catches missing required vars
make prod-config # the same, through the same wrapper
It is docker-compose format, not shell

Never source env/production.env. VIDRA_COMPOSE_PROFILES=core frontend is correct for Compose's --env-file parser and catastrophic for a shell, which sets the variable to core and then tries to execute frontend. Parse it as KEY=VALUE with the value literal, or hand it to docker compose --env-file.

Layer 2: the instance settings registry

internal/instancesettings is a typed, validated, runtime-mutable database overlay. Each setting carries page and section metadata, and the admin configuration UI is rendered from that metadata rather than hand-written. Over a hundred settings are registered today.

A change takes effect without a restart: runtime knobs reach workers through provider-func closures read at request or job time, and the server reloads its overlay when a value is written.

The authoritative list is the registry itself:

Read the effective values from a running instance instead of from a document:

curl -s https://example.com/api/v1/admin/system \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.'

/api/v1/admin/system returns the effective non-secret configuration alongside dependency health, which is the honest answer to "what is this instance actually running with".

Effective capability is the setting AND the boot config

This is the one rule that explains most "the toggle did not do anything" reports. A setting can only enable something the deployment actually has. Turning captions on in the admin console does nothing if the captions profile never started the Whisper service; enabling the IPFS mirror does nothing without the ipfs profile and a reachable node.

Check both halves: the compose profiles in VIDRA_COMPOSE_PROFILES, and the setting in the console.

Secret rotation costs

Not every secret is equally cheap to change. There is no re-wrap job — nothing re-encrypts persisted rows under a new key-encryption key.

SecretCost
JWT_SECRETFree — logs everyone out. Also re-derives the playback-token signer, so outstanding ?pt= links stop working. Rotate off-peak.
SMTP_PASSWORDFree. Change at the relay and in the env file, then up -d api.
STORAGE_S3_ACCESS_KEY / _SECRET_KEYFree. Create the new key, deploy, then delete the old one — not the other way round.
LIVE_INGEST_SECRETFree. Breaks in-flight RTMP sessions; new stream keys work immediately.
POSTGRES_PASSWORD / REDIS_PASSWORDFree, but two places: the value and any explicit DSN that embeds it. Postgres needs ALTER ROLE … PASSWORD if the volume already exists.
SEARCH_INTERNAL_SECRETMust change on both services in one deploy. A split rollout means every core-to-search call 401s until it converges.
FEDERATION_KEY_KEKDestructive. Persisted federation actor keys become unreadable — and via the fallback below, stored ATProto app passwords and TOTP secrets too.
MFA_KEY_KEKDestructive. Every stored TOTP secret becomes undecryptable and every 2FA user is locked out. Leaving it unset stores TOTP secrets in plaintext, warned about only at boot.
ATPROTO_KEY_KEKDestructive. Every linked Bluesky app password becomes undecryptable; affected users must re-link.
The KEK fallback

ATProtoKEK() and MFAKEK() both fall back to FederationKeyKEK when their own value is unset. Rotating FEDERATION_KEY_KEK on an instance that never set the other two silently destroys those too. Set all three explicitly.

If you must rotate an envelope key, the only safe procedure today is: set the new key, accept that the old ciphertext is dead, and force the affected users through re-enrolment. Plan it as a user-visible event, not an ops task.