Backups
What a backup is
A backup is a byte-exact snapshot of a workload's volume — a gzipped tar, taken with the instance briefly suspended so a database on disk is consistent. It is per-workload; a project backup fans out over the project's workloads.
What is not in it: everything derived. node_modules, .git, build output, caches — the
paths a template declares in backup_exclude. Dependencies come from an install step or an
image, never from a backup. That keeps snapshots small enough to take often, and it is the
same rule locally and in production.
Taking and restoring
# one workload
curl -X POST .../v1/workloads/<id>/backups -H "Authorization: Bearer $ORCHD_KEY"
# every workload in a project
curl -X POST .../v1/projects/<id>/backups -H "Authorization: Bearer $ORCHD_KEY"
# list
curl .../v1/backups -H "Authorization: Bearer $ORCHD_KEY"
curl .../v1/workloads/<id>/backups -H "Authorization: Bearer $ORCHD_KEY"
# restore: replaces the workload's current data, then reboots it
curl -X POST .../v1/workloads/<id>/restore -H "Authorization: Bearer $ORCHD_KEY" \
-d '{"backup_id":"<id>__20260719T…"}'
Restore is destructive by definition — the volume is replaced with the snapshot's contents, so anything written since is gone. The workload is rebooted afterwards.
Scheduled backups run on ORCHD_BACKUP_INTERVAL (0 disables the schedule; on-demand still
works), keeping ORCHD_BACKUP_RETAIN per workload.
Database dumps
ORCHD_BACKUP_DUMP_TYPES=tinbase-project backs databases up as a tinbase export
(tinbase db export, tinbase 0.22+) instead of a tar of the data directory. orchd runs the
export inside the workload's own container. It is a pg_dump plus roles and stored files,
usually KBs where the data directory is tens of MB, and it is taken without pausing the
database.
- When: as the workload goes idle (while it still runs), daily while it runs, and on demand. A suspended database is not woken for the daily backup: its idle-time dump already holds it.
- What is kept:
- Nothing, when the database still holds only what its migrations and seed put there (tinbase's seed snapshot). Re-creating it from the project's files gives the same database.
- Nothing new, when the dump's
contentHashmatches the last stored one.
- Eviction: once the idle-time dump is taken and the database stops, orchd records a
fingerprint of its
pg_control. Postgres rewrites that file at every startup and shutdown, so while it matches, the database has not run since and eviction relies on the dump as is. If not (the dump failed, or the database ran again), eviction falls back to a tar of the data directory. - Restore:
POST …/restoreplaces the archive at.tinbase/restore.tar.gzin the emptied data directory and boots the workload. tinbase loads it before it opens the database, and refuses to start if it does not load.
Backups list with "kind": "dump"; a data-directory tar has no kind.
Where they go
The target is an adaptor, switchable at runtime from Settings (no redeploy):
| Target | Notes |
|---|---|
| Local directory | ORCHD_BACKUP_DIR, default <data-root>/backups. Fine for development; on one box it is not off-box durability. |
| S3 / R2 | Endpoint, bucket, region, prefix and credentials. SigV4 is hand-rolled with no SDK, so it works against S3, R2, B2 and MinIO alike. |
curl -X PUT .../v1/settings/backup -H "Authorization: Bearer $ORCHD_KEY" -d '{
"type": "s3",
"endpoint": "https://<account>.r2.cloudflarestorage.com",
"bucket": "backups", "region": "auto", "prefix": "backups",
"access_key": "…", "secret_key": "…"
}'
Switching to S3 does not strand what is already on local disk. While ORCHD_BACKUP_DIR holds
archives, they stay listed, restorable and pruned beside the new ones until retention drops
them, and new backups go to S3.
The secret is stored server-side and never returned — GET /v1/settings reports
backup_secret_set instead. Leave secret_key blank on an update to keep the existing one.
The control-plane index
The project/workload index is small and irreplaceable, so it is backed up too — on the same schedule, and on demand:
curl -X POST .../v1/system/backup -H "Authorization: Bearer $ORCHD_KEY"
It is stored under the reserved key _control-plane. With the SQLite store the WAL is
checkpointed first so the snapshot is consistent. Restoring it is a deliberate manual
operation (fetch and extract) rather than an endpoint, because it re-seeds the entire
control plane.
Limits worth knowing
- The brief suspend. A data-directory tar means pausing the workload for the duration of the tar. Database dumps (above) do not pause. Hot / WAL-based backups for other types are on the roadmap.
- No point-in-time recovery. You restore to a snapshot, not to a timestamp.
- Backups do not survive a project delete. Deleting a project reclaims its data; keeping snapshots around as an undo buffer is future work.
- One target for the instance. Per-region backup targets are part of the multi-node milestone, not today.