Backups and restore
Kryton’s state lives in two places: Postgres (notes content, vector embeddings, users, sessions, attachments metadata) and the notes directory on disk (note files). A working backup covers both at consistent points in time.
Docker Compose
Section titled “Docker Compose”Postgres dump
Section titled “Postgres dump”Run from the directory containing your compose file. The bundled Postgres image is reachable as the postgres service:
docker compose exec -T postgres \ pg_dump -U kryton --format=custom kryton > kryton-$(date -u +%Y%m%dT%H%M%SZ).dumpNotes:
--format=customispg_restore-compatible and compresses by default.- The
-Tflag (docker compose exec -T) disables TTY allocation so the redirect captures only the dump. - Add
--no-owner --no-aclif you intend to restore into a different role.
Notes directory
Section titled “Notes directory”The notes bind mount is ./notes on the host. Tar it alongside the dump:
tar czf notes-$(date -u +%Y%m%dT%H%M%SZ).tar.gz notes/Consistency between the two
Section titled “Consistency between the two”The notes filesystem and the Postgres state are not transactionally linked, but both are append-mostly. Taking the pg_dump first, then the tar, gives you a database that may reference a notes file that isn’t in the archive — usually harmless on restore (the server treats missing files as not-yet-synced). Taking the tar first is also safe.
For stronger consistency, stop the kryton service briefly:
docker compose stop krytondocker compose exec -T postgres pg_dump -U kryton --format=custom kryton > kryton.dumptar czf notes.tar.gz notes/docker compose start krytonRestoring Docker Compose
Section titled “Restoring Docker Compose”Start with an empty target stack — the database must not exist yet. From a clean directory:
-
Place
docker-compose.prod.ymland your.env. -
Start Postgres only:
Terminal window docker compose up -d postgres -
Drop and recreate the database (
pg_restoredoes not overwrite by default):Terminal window docker compose exec -T postgres dropdb -U kryton --if-exists krytondocker compose exec -T postgres createdb -U kryton krytondocker compose exec -T postgres psql -U kryton -d kryton -c 'CREATE EXTENSION IF NOT EXISTS vector;' -
Load the dump:
Terminal window docker compose exec -T postgres pg_restore -U kryton -d kryton --no-owner --no-acl < kryton.dump -
Unpack the notes archive into the host directory the compose file mounts:
Terminal window tar xzf notes.tar.gz # writes ./notes/… -
Start kryton:
Terminal window docker compose up -d krytonThe entrypoint runs
migrate-prod.mjsagain — pending migrations against the restored schema are applied automatically.
Restore order matters:
vectorextension beforepg_restore(the dump references vector-typed columns), then restore, then start the server so migrations run cleanly against the restored schema. The bundledpgvector/pgvector:pg16image ships the extension binaries; theCREATE EXTENSIONstep turns it on.
The chart does not provide a backup.* block in values.yaml — there is no Helm-level backup primitive. Three options:
- Run the operator (next section) — managed CronJob + S3-compatible target.
- Roll your own CronJob — a
batch/v1CronJob in the same namespace that execs into the Postgres pod and uploads the dump somewhere durable. The operator’sinternal/controller/backup.gois a working reference implementation you can copy. - Volume snapshots — if the Postgres PVC’s StorageClass supports CSI snapshots, schedule
VolumeSnapshotobjects against<release>-postgresql. For the notes PVC do the same against the chart’s PVC (<release>by default).
The chart’s livenessProbe is /healthz and readinessProbe is /readyz — your backup tooling can read these to gate before/after windows.
Operator (Kryton CR)
Section titled “Operator (Kryton CR)”The operator ships two relevant primitives: spec.backup (pg_dump → S3-compatible) and spec.snapshot (CSI VolumeSnapshot of the persistence PVC).
spec.backup
Section titled “spec.backup”apiVersion: kryton.azrtydxb.io/v1alpha1kind: Krytonmetadata: name: kryton-prodspec: version: "4.5.0" backup: schedule: "0 3 * * *" # 03:00 UTC daily retention: "30d" # mc --older-than syntax: 30d, 12h, … objectStore: bucket: kryton-backups endpoint: https://minio.example.com region: "" # optional; mc still reads the field prefix: kryton-prod/ # defaults to <cr-name>/ credentialsSecretRef: name: kryton-backup-credsPrerequisites:
kryton-backup-credsSecret withOBJECT_STORE_ACCESS_KEY/OBJECT_STORE_SECRET_KEYkeys.- The chart’s bundled Postgres (or an external one whose credentials are in a Secret named
<cr-name>-postgresqlwith keysPGHOST/PGPORT/PGUSER/PGPASSWORD/PGDATABASE). - An S3-compatible bucket; the endpoint URL is explicit, so MinIO / Garage / SeaweedFS all work.
What the operator emits (operator/internal/controller/backup.go):
- A CronJob
<cr-name>-backup, imagepostgres:16, withconcurrencyPolicy: Forbid,backoffLimit: 2. - The Job script installs
mcat runtime (curl-fetched fromdl.min.io), runspg_dump --format=custom --no-owner --no-acl, uploads to<bucket>/<prefix><database>-<timestamp>.dump, and sweepsmc rm --recursive --force --older-than $RETENTION.
spec.snapshot
Section titled “spec.snapshot”spec: snapshot: schedule: "0 4 * * *" retention: "7d" volumeSnapshotClassName: csi-hostpath-snapclassPrerequisites:
- A CSI driver with snapshot support.
- The named
VolumeSnapshotClass. - A pre-existing ServiceAccount
<cr-name>-snapshotwith RBAC to create/deletevolumesnapshots— the operator does not provision this for you.
What it emits: a CronJob <cr-name>-snapshot, image bitnami/kubectl:1.31, that kubectl applys a snapshot.storage.k8s.io/v1 VolumeSnapshot and sweeps older ones past retention.
Restoring from an operator backup
Section titled “Restoring from an operator backup”The dumps are plain pg_dump --format=custom files in the bucket. The restore drill mirrors the Compose flow against the in-cluster Postgres pod:
# Download the dump to a workstation, then load via a one-shot pod:kubectl run --rm -i --tty pg-restore --image=postgres:16 --restart=Never -- \ bash -c 'apt-get update && apt-get -y install postgresql-client; \ pg_restore -h <release>-postgresql -U kryton -d kryton --no-owner --no-acl < /tmp/kryton.dump'Practical workflow: kubectl-copy the dump into a temporary pod with mc (or aws s3 cp if you stored to AWS), then pg_restore from inside that pod. Drop + recreate the database first (same dropdb / createdb / CREATE EXTENSION vector sequence as the Compose path).
For PVC restore from a VolumeSnapshot, create a new PVC with dataSource: <snapshot> and re-point the chart to that PVC via persistence.existingClaim.
Testing a restore
Section titled “Testing a restore”Cheap drill that catches the usual breakage:
- Take a dump + notes tarball.
- Start a throwaway compose stack in a different directory with a different volume namespace (
docker compose -p kryton-restore up -d postgres). - Restore into it.
docker compose -p kryton-restore up -d kryton— confirm/api/versionanswers and you can read a recent note.docker compose -p kryton-restore down -vto clean up.
A restore that hasn’t been exercised does not work.