§02 — Exo Control Plane

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.

5 min read·Set by Exo Editorial·v0.3.0 Beta

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

backend env· text
1Variable Required Default Purpose
2───────── ──────── ─────── ───────
3EXO_ENCRYPTION_KEY ✓ yes (none) AES-256 root key, exactly 32 bytes
4EXO_JWT_SECRET ✓ prod changeme-secret-key (dev) HS256 key for user + OAuth tokens
5EXO_DATABASE_DSN ✓ prod localhost dev DSN Postgres connection string
6EXO_PORT no 9092 HTTP listen port
7EXO_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 prod
10EXO_CAST_STORAGE_DIR no /var/lib/exo/casts Session-recording dir (writable vol)
11EXO_CLAUDE_API_KEY no (empty) base64 Anthropic key, AI fallback

EXO_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.

terminal· bash
1printf '%s' 'sk-ant-...' | base64 # value for EXO_CLAUDE_API_KEY

Generating 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:

terminal· bash
1EXO_ENCRYPTION_KEY="$(openssl rand -hex 16)" # 32 hex chars = 32 bytes
2EXO_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"