§02 — Exo Control Plane

Database · Postgres

Exo's only stateful dependency. What version, how the schema is created and maintained, and the one extra volume the backend needs beyond Postgres.

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

Durable control-plane state — users, tokens, audit events, deployments, runtime statuses — lives in PostgreSQL. The backend owns its schema end to end: it migrates, partitions, and bootstraps the encryption keyring at boot, so there's no separate migration job to run or sequence.

Version & DSN

Use PostgreSQL 16. The connection string is supplied via EXO_DATABASE_DSN in either libpq keyword form or a URL:

EXO_DATABASE_DSN· text
1# keyword form
2host=db.example.com user=exo password=… dbname=exo port=5432 sslmode=require
3 
4# URL form
5postgres://exo:…@db.example.com:5432/exo?sslmode=require

The backend keeps a pool of up to 100 open / 10 idle connections.

Bundled vs managed

  • Bundled (trials). exo-install can deploy an in-cluster postgres:16-alpine StatefulSet and wire the DSN for you. The Docker Compose stack does the same locally. Fine for evaluation, not for production durability.
  • Managed (production). Point at RDS / Cloud SQL / a managed Postgres with --postgres=false --db-dsn "…". You get backups, failover, and PITR from your provider.

Migrations at boot

On startup the backend runs, in order:

  • Partition-parent creation (tables declared PARTITION BY).
  • Encrypted-column migration (e.g. tool-status columns jsonb → text).
  • GORM auto-migrate for all models.
  • Post-migrate SQL constraints (case-insensitive email uniqueness, cascade FKs).
  • Encryption-keyring bootstrap — requires EXO_ENCRYPTION_KEY.
  • Monthly child-partition creation + dropping expired partitions, then a daily maintenance daemon for month rollovers and retention.

Partitioning & retention

High-volume tables (session events, audit) are time-partitioned by month. The maintenance daemon rolls new partitions forward and drops ones past the retention window. Retention is configurable per deployment from the dashboard (or exo deployment retention <id> --days N); the daemon enforces it without manual SQL.

Session-recording storage

Asciicast session recordings are written to the filesystem, not Postgres, under EXO_CAST_STORAGE_DIR (default /var/lib/exo/casts, up to 64 MiB per recording). In Kubernetes this path needs a writable volume — mount a PersistentVolume if you want recordings to survive pod restarts.

cast volume (excerpt)· yaml
1 volumeMounts:
2 - name: casts
3 mountPath: /var/lib/exo/casts
4 volumes:
5 - name: casts
6 persistentVolumeClaim:
7 claimName: exo-casts

Backups

Back up Postgres with your provider's tooling (or pg_dump for the bundled instance). Critically, back up EXO_ENCRYPTION_KEY separately — a database backup is unreadable without it. See Config & secrets.