Backup and restore ​
CodeLotse keeps its state in PostgreSQL. A complete backup has three parts that must be restored together:
| Part | Compose location | Why |
|---|---|---|
PostgreSQL database quavon | volume quavon_postgres-data | All tenants, repositories, reviews, settings, audit log and encrypted credentials |
| Secrets | deploy/compose/secrets/ | encryption_key decrypts GitHub App credentials, LLM keys, provider credentials and session tokens; the role passwords open the database |
| Settings | deploy/compose/.env | Domain, TLS mode, ports, GitHub URL, profiles |
Without encryption_key the encrypted data cannot be recovered. Keep previous encryption keys as long as any backup still needs them (operations.md). Store backups encrypted and off the host; they contain personal data and secrets. There is no quavon backup command; use the commands below.
What is not backed up ​
Valkey holds only queues and short-lived data: job queues, rate-limit and budget counters, caches, installation tokens, login and OAuth state, sandbox jobs. It is not part of the backup. After Valkey data is lost:
| Data | Result |
|---|---|
| Jobs not yet handed to the queue | Kept in the PostgreSQL outbox and dispatched when Valkey is back (ADR-0005). |
| Webhook deliveries that were queued but not processed | Re-queued by the worker's delivery reconciliation (every 10 minutes, for deliveries older than 15 minutes). For GitHub, failed deliveries of the last 6 hours are also requested again from GitHub. |
| Other queued or running jobs (reviews, triage, replies, fixes, syncs, publishing) | Lost. Stale runs are marked as failed; the next push starts a new review. Publishing never posts twice. |
| Caches, tokens, counters | Rebuilt on demand. Rate limits start again from zero. |
| Login and OAuth flows in progress | Must be started again. Sessions are stored in PostgreSQL and stay valid. |
The code index tables (code_index_snapshots, code_index_files, code_blobs, code_chunks, code_symbols, code_chunk_embeddings) are part of the database backup. They can be rebuilt from the repositories, so you may leave out their data to keep backups small, for example pg_dump --exclude-table-data='code_*'. After a restore without them, the hourly maintenance rebuilds the index of every repository whose mode is not off; embeddings are computed again and cost model tokens.
The caddy-data volume holds the TLS certificates and the ACME account. Caddy obtains new ones if it is lost; back it up only if you want to avoid re-issuing certificates.
Docker Compose ​
Run the commands in deploy/compose. If the dex profile is enabled, add -f docker-compose.yml -f docker-compose.dex.yml after docker compose.
Back up ​
cd deploy/compose
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
docker compose -p quavon exec -T postgres pg_dump -U postgres -d quavon -Fc > "quavon-$STAMP.dump"
tar -czf "quavon-config-$STAMP.tar.gz" secrets .envpg_dumptakes a consistent snapshot while the stack keeps running.- The dump uses PostgreSQL's custom format; restore it with
pg_restoreof the same or a newer major version (the bundled image is PostgreSQL 17). - Record the CodeLotse version (
git describe --tagsor the commit) with the backup. Restore into the same version, then upgrade. - Test a restore regularly on a separate host.
Restore ​
Restore into the same CodeLotse version that created the backup.
Put
secrets/and.envfrom the backup intodeploy/compose/(keep mode 0700 onsecrets/). On a new host, check out the same version first.Stop the services that use the database and make sure the roles exist:
shdocker compose -p quavon stop api worker docker compose -p quavon run --rm initOn an empty volume
initalso creates an empty schema; it is replaced in the next step.Replace the database with the dump:
shdocker compose -p quavon exec -T postgres dropdb -U postgres quavon docker compose -p quavon exec -T postgres createdb -U postgres -O quavon_migrator quavon docker compose -p quavon exec -T postgres pg_restore -U postgres -d quavon --exit-on-error < quavon-<STAMP>.dumpStart everything.
initruns again, re-applies the role settings and finds no new migrations:shcd ../.. && ./deploy/compose/quavon install --yes ./deploy/compose/quavon doctor
doctor must not report QD202 (passwords do not match the secrets), QD205 (missing migrations) or QD403 (values that cannot be decrypted). Each of them means the secrets and the dump are from different installations; see doctor.md.
Kubernetes (Helm) ​
Back up the generated Secret (codelotse-secrets for the release codelotse; your own Secrets if you set secrets.<name>.existingSecret) and your values file together with the database. With the bundled PostgreSQL (release codelotse in namespace codelotse):
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
kubectl -n codelotse exec codelotse-postgres-0 -c postgres -- pg_dump -U postgres -d quavon -Fc > "quavon-$STAMP.dump"
kubectl -n codelotse get secret codelotse-secrets -o yaml > "codelotse-secrets-$STAMP.yaml"To restore, scale api and worker to zero, recreate the database and run pg_restore in the PostgreSQL pod as in the Compose steps, then run helm upgrade with the same values so that db-init re-applies the role settings.
External PostgreSQL ​
With a managed or external PostgreSQL use its backup features (snapshots, point-in-time recovery) or pg_dump/pg_restore with an admin role. The restore needs the roles quavon_migrator, quavon_app and quavon_system with the passwords from your secrets; init or db-init creates or updates them when it can connect with the admin URL. The database must be owned by quavon_migrator.