Documentation for v2.6.1. Read the latest, v2.12.0.
Operator
Backup and restore
Sanshain keeps everything in one database, so a backup is a copy of that database and a restore is
putting the copy back. How the copy is taken depends on the engine: on SQLite the service produces
it, on PostgreSQL pg_dump does.
What changed in 2.6.0
-
GET /admin/backup/sqlitestreams a consistent snapshot while the service runs, with a button on the admin page. -
The Helm chart's
backup.*values render a nightly CronJob for either engine; Kustomize users get the same job as commented examples.
What is in the database#
Specs and their endpoint snippets, version lines, dependencies, branches and timeline, users, password hashes, sessions, API tokens, the authentication configuration including the LDAP bind password and the OIDC client secret, webhook secrets and the delivery log, the audit log, every setting. A backup is as sensitive as the instance; keep it where only operators can read it.
SQLite#
Do not copy sanshain.db from the volume while the service runs: with the write-ahead
log open a file copy can be inconsistent. Sanshain produces the copy itself:
curl -fsS -H "Authorization: Bearer $SANSHAIN_TOKEN" \
-o sanshain-backup.db "$SANSHAIN/admin/backup/sqlite"
-
The engine writes the snapshot (
VACUUM INTO) to a temporary file next to the database, streams it asapplication/vnd.sqlite3withContent-LengthandContent-Disposition: attachment; filename="sanshain-2.6.0-20261018T020000Z.db"(instance version, UTC timestamp), and removes the file when the download ends, also when the client disconnects. The data volume needs room for one extra copy. -
The route needs
run_destructive_operations(admins and root). It is the one administrative route that admits an API token: afulltoken of such an account works, aread_onlytoken answers403. Mint it on/account.html; it is the only credential the job holds. -
One snapshot at a time: a second request answers
409A database snapshot is already being produced; retry when it has finished. -
On PostgreSQL the route answers
409This instance runs PostgreSQL, which has no snapshot download; take backups with pg_dump (see the deployment guide, Backup and restore), and the button is absent. -
Every download is a
DATABASE_SNAPSHOTaudit entry with the acting user, the file name and the size.
The same snapshot is Download database snapshot under Database Configuration on the admin page.
The Helm CronJob#
With backup.enabled the chart renders one CronJob that fetches the snapshot to a
backup volume and keeps the newest retain files:
backup:
enabled: true
schedule: "0 2 * * *" # cluster time zone
retain: 7
# existingClaim: nightly-backups # otherwise the chart creates a 5Gi claim
tokenSecret:
name: sanshain-backup-token # kubectl create secret generic sanshain-backup-token --from-literal=token=<api token>
key: token
| Value | Default | Effect |
|---|---|---|
backup.enabled |
false |
Render the CronJob and, without existingClaim, its claim. |
backup.schedule |
"0 2 * * *" |
Cron schedule; concurrencyPolicy: Forbid. |
backup.engine |
"" |
sqlite or postgres; empty derives it from
secrets.databaseUrl (postgres:// or postgresql://
means PostgreSQL).
|
backup.retain |
7 |
Files kept on the volume; older ones are deleted after each successful run. Below 1 the chart refuses to render. |
backup.existingClaim |
"" |
Mount this claim instead of creating one. |
backup.storage.size, storageClass, accessMode |
5Gi, "", ReadWriteOnce |
The claim the chart creates. |
backup.tokenSecret.name, key |
"", token |
SQLite only: the Secret holding the API token. Required; the chart fails without it. |
backup.images.curl, backup.images.postgres |
curlimages/curl:8.11.1, postgres:17-alpine |
The job images for the two engines. |
The SQLite job calls the service inside the cluster
(http://<release>:<service.port>/admin/backup/sqlite), writes
/backup/sanshain-<UTC timestamp>.db through a .part file, then
prunes. Kustomize users find the same job, with its claim and the Secret command, as a commented
example in deploy/kubernetes/backup-cronjob-sqlite.yaml; uncomment it and add the
file to resources.
Restore#
- Scale Sanshain to zero (
kubectl scale deployment sanshain --replicas=0). -
On the data volume replace
sanshain.dbwith the snapshot and deletesanshain.db-walandsanshain.db-shmif present. - Scale back up. The migration runner brings the snapshot's schema up to the running version, so a snapshot from an older instance version restores; a snapshot from a newer version does not.
PostgreSQL#
pg_dump --format=custom --file=sanshain.dump "$DATABASE_URL"
With Helm, the same backup section renders a pg_dump CronJob when the
engine is postgres; it reads DATABASE_URL from the chart's own Secret,
so no further credential is needed, and writes
/backup/sanshain-<UTC timestamp>.dump:
backup:
enabled: true
schedule: "0 2 * * *"
retain: 7
Kustomize: deploy/kubernetes/backup-cronjob-postgres.yaml, commented like the SQLite
one.
Restore#
- Scale Sanshain to zero.
pg_restore --clean --if-exists --dbname="$DATABASE_URL" sanshain.dump- Scale back up; migrations run on start as above.
Before an upgrade or a reset#
Take a backup before changing the image tag and before anything in the Danger Zone: migrations are forward-only and the resets have no undo. See Upgrades and migrations.
Checked against v2.6.0.