Backup and restore
What to back up
PostgreSQL is the only stateful component in a TokenTimer deployment. The API, dashboard, and workers are stateless; everything they need (tokens, workspaces, users, alert queue, audit and delivery logs, system settings) lives in the database.
That means a consistent database dump is a complete backup. Also keep a copy of your deployment configuration (.env for Compose, values file and Kubernetes Secrets for Helm) so you can rebuild the stack, but treat secrets according to your usual secret-handling policy rather than bundling them with data backups.
Session cookies are signed with SESSION_SECRET. If you restore to a new environment with a different secret, users simply log in again; no data is lost.
Docker Compose
The Compose stack runs PostgreSQL as the postgres service (container tokentimer-db), with database and user both named tokentimer by default.
Backup
docker compose exec postgres pg_dump -U tokentimer tokentimer > backup.sql
For scheduled backups, add a timestamp and compress:
docker compose exec postgres pg_dump -U tokentimer tokentimer | gzip > "tokentimer-$(date +%F).sql.gz"
Restore
docker compose exec -T postgres psql -U tokentimer tokentimer < backup.sql
Restore into an empty database. If the target database already contains data, drop and recreate it first (or restore into a fresh volume), otherwise the restore can fail on conflicts or leave a mixed state.
If you changed DB_USER or DB_NAME in your .env, use those values in the commands above.
Kubernetes (Helm / CloudNativePG)
The Helm chart provisions PostgreSQL as a CloudNativePG Cluster named <release>-pg by default (for a release named tokentimer, the cluster and its pods are tokentimer-pg).
Option A - CNPG-native backups (recommended)
If you enabled the chart's backup support (postgresql.cloudnative.backup.enabled), CloudNativePG handles base backups and WAL archiving to object storage (barman object store with S3 credentials and a retention policy). Prefer this for production: it gives you point-in-time recovery and does not depend on manual dumps.
Trigger an on-demand backup with a CNPG Backup resource, and restore by bootstrapping a new cluster from the object store (see the CloudNativePG recovery documentation for your operator version).
Option B - Manual pg_dump against the cluster pod
For ad-hoc dumps or when CNPG backups are not configured, exec into the current primary pod:
# Find the primary pod of the CNPG cluster
kubectl get pods -n tokentimer -l cnpg.io/cluster=tokentimer-pg
# Dump (adjust pod name to the current primary, e.g. tokentimer-pg-1)
kubectl exec -n tokentimer tokentimer-pg-1 -- \
pg_dump -U tokentimer tokentimer > backup.sql
Restore into an empty database:
kubectl exec -i -n tokentimer tokentimer-pg-1 -- \
psql -U tokentimer tokentimer < backup.sql
External PostgreSQL
If you pointed the chart at an external database (postgresql.external), use your existing backup tooling for that instance; TokenTimer adds no extra state outside it.
PVCs on uninstall
helm uninstall retains the CloudNativePG PVCs by default. Your data survives an accidental uninstall, but a retained PVC is not a backup: it lives on the same storage as the cluster. Delete retained PVCs manually only once you have confirmed backups elsewhere.
Recommended cadence and retention
| Environment | Cadence | Retention |
|---|---|---|
| Production | Daily automated dump or continuous CNPG WAL archiving | 30 days, plus monthly archives per your compliance needs |
| Staging / lab | Weekly, or before risky changes | 7 days |
Whatever the schedule, periodically test a restore into a scratch environment. A backup you have never restored is not verified.
Before every upgrade
Always take a backup before major upgrades or database migrations:
# Compose
docker compose exec postgres pg_dump -U tokentimer tokentimer > pre-upgrade-backup.sql
# Kubernetes (CNPG)
kubectl exec -n tokentimer tokentimer-pg-1 -- \
pg_dump -U tokentimer tokentimer > pre-upgrade-backup.sql
Then follow the upgrade steps in the install runbook.