Backups and restore tests
A backup you have never restored is a guess. This page is the whole-box backup an operator keeps, plus the habit that turns it from a guess into a guarantee: restoring it somewhere safe on a schedule.
There is a second, finer-grained backup that lives inside the app (a restorable copy of one workspace, optionally pushed to Google Drive). That is covered in Backup and export. This page is the box underneath it.
What to back up
All of the instance's state lives in bind-mounted folders under ./data/:
Postgres, uploaded files, installed modules, and Caddy's certificates. Copying
that whole tree is a complete backup:
tar czf cobblr-data-$(date +%F).tar.gz ./data
Do this with the stack stopped, 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 that does not need the stack stopped, take a logical dump instead.
A database dump
docker compose exec db pg_dumpall -U cobblr | gzip > cobblr-backup-$(date +%F).sql.gz
pg_dumpall captures every database on the instance, including each workspace's
own database, in one file. Gzip keeps it small. This is the backup to schedule,
because it is clean, portable, and easy to restore into a fresh Postgres.
Keep more than one, and keep one off the 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.
Automating the dump is a cron entry on the host that runs the command above and prunes old files. 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.
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), because 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; 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.