Control-plane config & secrets
Every environment variable the Exo backend reads, which ones are mandatory, and how to generate the two secrets that gate startup.
The backend is configured entirely through environment variables — typically a ConfigMap for the non-secret ones and a Secret for the rest. Two of them are mandatory and the process fails fast without them: there are no production-safe defaults for a signing key or an encryption key.
Environment variables
1Variable Required Default Purpose2───────── ──────── ─────── ───────3EXO_ENCRYPTION_KEY ✓ yes (none) AES-256 root key, exactly 32 bytes4EXO_JWT_SECRET ✓ prod changeme-secret-key (dev) HS256 key for user + OAuth tokens5EXO_DATABASE_DSN ✓ prod localhost dev DSN Postgres connection string6EXO_PORT no 9092 HTTP listen port7EXO_BASE_URL no http://localhost:9092 Public URL (email links, OAuth cb)8EXO_JWT_EXPIRY no 24h User JWT lifetime (Go duration)9EXO_DEV_MODE no true Debug logging; set "false" in prod10EXO_CAST_STORAGE_DIR no /var/lib/exo/casts Session-recording dir (writable vol)11EXO_CLAUDE_API_KEY no (empty) base64 Anthropic key, AI fallbackEXO_ENCRYPTION_KEY
This is the root of the envelope-encryption keyring. It wraps the key-encryption key, which in turn encrypts every sensitive column at rest — integration credentials, identity-provider secrets, log-sink configs, tool permissions, session payloads. A database dump is useless without it.
EXO_JWT_SECRET
Signs every user JWT and every short-lived OAuth access token (HS256). Rotating it invalidates all active sessions and issued tokens immediately, so treat it like a database password: vault it, rotate on a schedule, never commit it. The dev default (changeme-secret-key) is for local runs only — anyone with the same value can forge tokens against your instance.
EXO_CLAUDE_API_KEY
Optional. A platform-default Anthropic key used by the "Generate with AI" features (skill/guardrail generation) when a tenant hasn't configured its own provider. It must be base64-encoded in the Secret — the backend decodes it at read time. Leave it blank to disable the fallback; prefer Claude Workload Identity Federation over a static key in production.
1printf '%s' 'sk-ant-...' | base64 # value for EXO_CLAUDE_API_KEYGenerating secrets
If you use exo-install, the JWT secret, encryption key, and (with the bundled Postgres) the database password are generated for you. For manual installs:
1EXO_ENCRYPTION_KEY="$(openssl rand -hex 16)" # 32 hex chars = 32 bytes2EXO_JWT_SECRET="$(openssl rand -base64 48)"3 4kubectl -n exo create secret generic exo-secrets \5 --from-literal=EXO_ENCRYPTION_KEY="$EXO_ENCRYPTION_KEY" \6 --from-literal=EXO_JWT_SECRET="$EXO_JWT_SECRET" \7 --from-literal=EXO_DATABASE_DSN="host=db user=exo dbname=exo sslmode=require"