Backups and restore tests
The stack already dumps the database nightly. Your job is the off-box copy and the restore test, because a backup you have never restored is a guess.
- Let the nightly dump run (it already does).
- Pull
data/backups/to another machine or drive on a schedule. - On a schedule (monthly is a sensible floor), restore the latest dump into a throwaway database and check the data is really there.
A second, finer-grained backup lives inside the app (a restorable copy of one workspace, optionally pushed to Google Drive): see Backup and export. This page is the box underneath it.
What to back up
Copy the whole ./data/ tree, because it holds all of the instance's state:
tar czf cobblr-data-$(date +%F).tar.gz ./data
- Everything is bind-mounted under
./data/: Postgres, uploaded files, the stack's own nightly dumps, installed modules, and Caddy's certificates. Copying that whole tree is a complete backup. - Stop the stack first, or accept that a live copy of the database files is a crash-consistent snapshot rather than a clean one.
- For a clean, self-contained database copy without stopping, take a logical dump instead (below).
The nightly dump the stack already takes
pg_dumpallruns once a day (thebackupcontainer in the stack), intodata/backups/daily/.- A weekly copy is kept on Sundays, and pruning keeps 30 daily and 12 weekly files.
- A dump below a sanity floor is discarded as failed rather than kept, so an empty file can never masquerade as a good night.
- Retention and the floor are configurable.
- It captures every database on the instance, including each workspace's own database, in one file, and it is clean while the stack runs, with nothing to schedule and nothing to stop.
To take one by hand at any moment:
docker compose exec db pg_dumpall -U cobblr | gzip > cobblr-backup-$(date +%F).sql.gz
Keep more than one, and keep one off the box
The rotation is already handled on the box, but the off-box copy is yours. A cron
entry on another machine that pulls data/backups/ (rsync over ssh, rclone to
a cloud drive) is enough. The in-app path can also push a workspace backup to
Google Drive on a schedule once you configure the operator credentials for it.
See Backup and export.
Why a rotation, and why off-box
A single overwritten backup fails you the moment a bad backup overwrites a good one. Keep a rotation (a run of dated daily dumps, thinned to weekly further back) so you can reach past a problem you did not notice immediately. And keep at least one copy on a different machine or drive: a backup that lives only on the box it protects is gone with the box.
Prove it restores
Restore into a disposable database and check the data is really there. Nothing touches your live instance:
# Start a throwaway Postgres from the same image your stack runs.
docker run -d --name cobblr-restore-test \
-e POSTGRES_PASSWORD=throwaway -p 54329:5432 \
ghcr.io/cobblrhq/cobblr-db:${COBBLR_VERSION:-latest}
# Load the most recent dump into it.
gunzip -c cobblr-backup-$(date +%F).sql.gz | \
docker exec -i cobblr-restore-test psql -U postgres
# Sanity-check: list the restored databases.
docker exec cobblr-restore-test psql -U postgres -c '\l'
# Tear it down.
docker rm -f cobblr-restore-test
- Use the same database image your stack runs (the command above reads your
pinned
COBBLR_VERSION): a workspace dump can rely on a Postgres extension a stock image does not carry, and a restore that silently skips it is not a real test. - Run this on a schedule, monthly is a sensible floor, so silent backup corruption is caught while it is an annoyance rather than a disaster. This is the step people skip and regret, and it is also the difference between "I have backups" and "I can recover".
- When a real recovery is what you need, Rollback covers pointing the live instance at a restored copy.