Data and backups
All of your instance's state is in one place, which makes both backup and recovery a file operation. The routine backup commands are in Operating. This page is the recovery side and the one mistake that can't be undone.
Where the data is
Everything lives under ./data/ next to your compose file, one folder per
service:
data/dbis the databases (the platform database plus one per workspace).data/filesis uploads and images.data/backupsis the nightly database dumps the stack writes for you.data/caddyis issued certificates.data/modulesis installed module code.
No named volumes, so:
- A copy of
./data/taken while the stack is stopped is a full backup, and moving to a new disk is onersync. - Never copy
data/dbwhile Postgres is running: that produces a torn snapshot that may not restore. On a live instance, back up with the nightly dumps instead (below), or stop the stack first.
Restoring the whole instance
From a folder copy, restore is the reverse of backup: bring the stack down, put the tree back, bring it up.
docker compose down
mv data data.broken-$(date +%F) # keep the old tree until the restore is proven
cp -a /path/to/backup/data ./data
docker compose up -d
- Only delete
data.broken-*after you have logged in and seen your records. If the backup path was wrong or the copy failed, that directory is still your instance. - Restore the
.envanddocker-compose.ymlfrom the same backup too, so the config matches the data. In particular the.envyou restore must carry the sameTENANT_CREDS_ENCRYPTION_KEYthe data was encrypted with (see below).
Restoring from a database dump
If your backup is a SQL dump (one of the stack's nightly files under
data/backups/daily/, or a hand-run pg_dumpall) rather than a folder copy,
restore it into an empty database directory with the app stopped, so
nothing is writing mid-restore and nothing collides with objects that already
exist:
docker compose stop api web
mv data/db data/db.broken-$(date +%F)
docker compose up -d db # a fresh, empty Postgres
sleep 10
gunzip -c data/backups/daily/cobblr-<stamp>.sql.gz \
| docker compose exec -T db psql -U cobblr -d cobblr_meta
docker compose up -d
- A dump restores the databases but not uploaded files or certificates, so the folder copy is the complete one.
- Keep
data/db.broken-*until you have logged in and seen your records.
The encryption key
TENANT_CREDS_ENCRYPTION_KEY encrypts the per-workspace credentials Cobblr
stores (the keys and tokens you enter for integrations and connected
services).
- Losing it is unrecoverable. If you change the key, or restore a database without the matching key, every stored credential becomes undecryptable ciphertext.
- Keep a copy of the key somewhere off this box: a password manager, or an encrypted note. Store it wherever you keep the recovery material for anything else you couldn't rebuild.
- When you restore, restore the
.envthat goes with the data, so the key comes along.
What breaks, and how the key is used
The key is derived into an AES-256 key and used to encrypt those values at rest. After a key mismatch the data isn't corrupted and the rest of the instance runs, but each affected integration has to be reconnected with its secret re-entered. The reasoning and the operator-side discipline for this key are in Secrets and keys.
A backup didn't run
Fix: check the container. If data/backups is empty or stale, run
docker compose ps backup and docker compose logs backup. The usual causes
are a .env missing POSTGRES_PASSWORD changes, or a full disk.
What the stack does on its own:
- The
backupcontainer writesdata/backups/daily/cobblr-<stamp>.sql.gzonce a day, keeps a weekly copy on Sundays, and prunes to 30 daily and 12 weekly files (retention is configurable). - A dump smaller than a sanity floor is treated as failed rather than kept, because an empty "successful" dump is worse than none.
- The nightly dump lives on the same disk as the instance, so it survives a bad upgrade but not a dead drive. Copy it off the box on a schedule, which is the part you still own: see Backups and restore tests.
For restorable snapshots of a single workspace, and off-box destinations like Google Drive, use the in-app tools in Backup and export. Those are separate from the whole-instance folder copy: one is a workspace's own data on demand, the other is everything on the box.