BurnerByte layers configuration, each source overriding the one before it:
Built-in defaults — registered in internal/config/config.go
config.yaml — optional, searched in . and /etc/burnerbyte/, or at BB_CONFIG_PATH
Environment variables — prefixed with BB_, where the prefix replaces the dots in the key path (BB_SERVER_PORT → server.port, BB_SMTP_HOSTNAME → smtp.hostname)
Database overrides — applied at startup from the system_configs table for the mailer, storage, sso and platform groups, so setup-wizard and admin-UI values take precedence over the environment for those keys
DATABASE_URL, REDIS_URL, JWT_SECRET and ENCRYPTION_KEY are read without the BB_ prefix.
Note
A BB_* variable only works for a key the binary has registered. Every key in
this reference is registered, and TestConfigExampleCoversEveryRegisteredKey /
TestEnvExampleCoversEveryRegisteredKey keep config.example.yaml and
.env.example exhaustive. If you add a config key, register it with
SetDefault (or BindEnv) or its environment override is silently discarded.
Worth its own section, because until v1.24.1 the answer was not what this page
implied. Compose injects only the variables a service names, and api and
smtpd named thirteen. Everything else in your .env was read for
substitution and then dropped: setting BB_RATE_LIMIT_TRUSTED_PROXIES or
BB_METRICS_TOKEN there had no effect at all, and nothing said so.
Both services now carry env_file: .env with required: false, so the whole
BB_* surface reaches the containers and a clean checkout with no .env still
comes up. That needs Compose v2.24 or newer.
Three layers resolve, last one winning:
Where the value comes from
Notes
.env, via env_file
Every BB_* key on this page. The default path for anything not named below.
environment: in docker-compose.yml
Thirteen keys the stack must control: DATABASE_URL, REDIS_URL, JWT_SECRET, ENCRYPTION_KEY, FRONTEND_URL, API_BASE_URL, BB_CORS_ALLOWED_ORIGINS, BB_SMTP_HOSTNAME and the five BB_MINIO_* keys. These win over .env.
The system_configs table
The mailer, storage, sso and platform groups, applied at startup. Setup-wizard and admin-UI values win over both of the above for those keys.
Setting one of the thirteen
Those keys are set in environment: and therefore ignore your .env. Each has
its own Compose-level variable — SMTP_HOSTNAME rather than
BB_SMTP_HOSTNAME, CORS_ALLOWED_ORIGINS rather than
BB_CORS_ALLOWED_ORIGINS, EXTERNAL_DATABASE_URL, EXTERNAL_REDIS_URL,
EXTERNAL_MINIO_ENDPOINT — and those are read from .env. The indirection
exists because the container-side value differs from the host-side one:
BB_MINIO_ENDPOINT is localhost:9000 when you run the binaries yourself and
minio:9000 inside the stack, so a single key cannot serve both.
Running the binaries directly, none of this applies: make run-api sources
.env and every BB_* key works as written.
Hostname in the SMTP greeting and the MX/SPF target the API compares domains against during verification — set it on the API process too, or no domain will verify
Strongly recommended. 32-byte hex-encoded (64-char) AES-256-GCM key. When empty, SSO client secrets, SMTP passwords and storage credentials are written to the database in plaintext — the API still boots. Docker Compose forwards only the unprefixed name.
bcrypt hashing cost (higher is slower and stronger). The timing-equalising dummy comparison that hides user enumeration is derived from this value, so raising it stays safe
Two of the seven workers have hardcoded intervals and no config key:
Worker
Interval
Description
invite_expiry
24h
Emails inviters about invites expiring within 24 hours, then deletes lapsed ones
admin_stats
10s
Refreshes the cached platform stats streamed over /api/v1/ws/admin-stats
Every job runs once at startup before its ticker begins, and every API process
starts all seven — there is no leader election and no way to run an API instance
without them. See Production Deployment.
When set, /metrics also requires Authorization: Bearer <token>. The loopback/private-address check alone depends on your proxy overwriting inbound X-Forwarded-For and X-Real-IP; a token does not. Prometheus sends it via bearer_token
Re-enables ?token=<jwt> on the WebSocket endpoints. The browser client uses the one-time /ws/ticket flow instead; a token in a URL is captured by reverse-proxy access logs and browser history
security.require_email_verification
BB_SECURITY_REQUIRE_EMAIL_VERIFICATION
false
Refuse password logins until the address is confirmed. Off by default because enabling it on an existing deployment locks out every account created beforehand. The check runs after the password, so it never reveals whether an address exists
security.enforce_sso_for_system_admins
BB_SECURITY_ENFORCE_SSO_FOR_SYSTEM_ADMINS
false
Require SSO for accounts carrying is_system_admin. The org-level enforce_sso setting joins through org membership, so it never covers a system admin who belongs to no organisation — the account that can grant is_system_admin, transfer teams and read every audit trail. Enforced only once that admin has a linked SSO identity, so it cannot lock out the last administrator
BB_CONFIG_PATH overrides the search path entirely and points at a single file.
It is also where the first-run web installer writes, so reads and writes stay
consistent. Without it, config.yaml is searched in . then /etc/burnerbyte/.