Skip to content

Database · Postgres

Industric's only stateful dependency. What version, how the schema gets built and maintained, and the options for storing session recordings.

Durable control-plane state (users, tokens, audit events, deployments, runtime statuses) lives in PostgreSQL. Apply the backend schema with an explicit migration job before starting or upgrading the server. Normal startup loads the encryption keyring but performs no schema migrations.

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 · keyword form· text
host=db.example.com user=exo password=… dbname=exo port=5432 sslmode=require
EXO_DATABASE_DSN · URL form· text
postgres://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.
  • Managed (production). Point at RDS / Cloud SQL / a managed Postgres with --postgres=false --db-dsn "…". You get backups, failover, and PITR from your provider.

Run migrations before serving

Run the target release's backend binary with the same database and encryption configuration as the serving process:

migration job· bash
exo-server --migrations

The command applies pending schema work and exits. Wait for success before rolling out the server. In local source development, make db-migrate provides the migration entry point. The serving process does not silently repair an older schema; it can report query errors if this step was skipped.

In Kubernetes, keep application replicas at zero while a one-off Job runs the migration command from the target image with the application's Secret and ConfigMap. Wait for the database to be ready first. If migration fails, stop and inspect its logs; start application replicas only after success.

Sequence upgrades explicitly
Back up the database and preserve the encryption key before migration. Use the target image for the migration job, with the same Secret and database access as the application.

Partitioning & retention

Partition maintenance manages monthly children and retention for tables an operator has already partitioned. Migrations do not create partitions, and the maintenance loop skips non-partitioned tables. Review your database's actual layout before relying on partition-based retention.

Session-recording storage

By default, session recordings use local storage (EXO_CAST_STORE=local) 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

For object storage, set EXO_CAST_STORE=s3 and EXO_CAST_S3_BUCKET, with credentials, region, and endpoint settings for your storage service. Back up and manage retention for your recording volume or bucket separately from Postgres and external log sinks.

Backups

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