Skip to main content
Version: 2026.8.1

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/db is the databases (the platform database plus one per workspace).

  • data/files is uploads and images.

  • data/backups is the nightly database dumps the stack writes for you.

  • data/caddy is issued certificates.

  • data/modules is installed module code.

  • A copy of ./data/ taken while the stack is stopped is a full backup. No named volumes, so moving to a new disk is one rsync.

  • Never copy data/db while 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 .env and docker-compose.yml from the same backup too, so the config matches the data. In particular the .env you restore must carry the same TENANT_CREDS_ENCRYPTION_KEY the data was encrypted with (see below).

Restoring from a database dump​

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:

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. The folder copy is the complete one.
  • Keep data/db.broken-* until you have logged in and seen your records.
Why empty and stopped

An empty directory with the app stopped means nothing is writing mid-restore and nothing collides with objects that already exist.

The encryption key​

Losing TENANT_CREDS_ENCRYPTION_KEY is unrecoverable.

  • 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 .env that goes with the data, so the key comes along.
What the key protects and what happens without it

TENANT_CREDS_ENCRYPTION_KEY encrypts the per-workspace credentials Cobblr stores (the keys and tokens you enter for integrations and connected services). It is derived into an AES-256 key and used to encrypt those values at rest.

If you change the key, or restore a database without the matching key, every stored credential becomes undecryptable ciphertext. 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​

data/backups is empty or stale.

  1. Check the container: docker compose ps backup and docker compose logs backup.
  2. The usual causes are a .env missing POSTGRES_PASSWORD changes, or a full disk.
  • What the stack does on its own: the backup container writes data/backups/daily/cobblr-<stamp>.sql.gz once 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.