Backup & Restore

What holds your data in a self-hosted Conzent OCI install, how to back it up on a schedule, and how to restore it on a new server.

Backup & Restore

Everything Conzent stores lives in Docker volumes and one configuration file. This page covers what is worth backing up, how to automate it, and how to get it all back.

What actually holds your data

Docker Compose creates five named volumes. Only two contain anything you cannot rebuild.

Volume / file Contents Back it up?
oci-db-data MariaDB: sites, banners, consent logs, cookie categories, policies, users, scan results Yes — this is everything
app-sites-data Generated consent scripts Yes, though regenerable
.env Database password, scanner key, third-party credentials Yes — irreplaceable
app-public Built CSS/JS assets No — rebuilt from the image
app-var Cache and logs No
oci-redis-data Sessions, job queue, beacon buffer No — transient by design

scripts/backup.sh captures exactly the first three, plus .conzent-credentials if it still exists.

Consent scripts are included because restoring them keeps live banners working during the minutes between a restore and a regeneration — but they are not the source of truth. If they were lost entirely, php bin/oci scripts:regenerate rebuilds every one from the database.

Taking a backup

bash scripts/backup.sh

That writes backups/conzent-YYYYmmdd-HHMMSS.tar.gz containing the full database dump, the generated consent scripts, your .env, and a manifest recording when it was taken and from which APP_URL.

bash scripts/backup.sh --output /mnt/backups     # write somewhere else
bash scripts/backup.sh --keep 14                 # keep only the 14 newest archives

The dump uses --single-transaction, so InnoDB tables are captured consistently without blocking writes. There is no need to stop the application first.

The archive contains your database and your secrets. It is written mode 600. Treat it like a password file: store it off the server, and encrypt it if it lands anywhere shared.

Automating it

A nightly backup at 03:00, keeping two weeks:

0 3 * * * cd /path/to/conzent && /bin/bash scripts/backup.sh --keep 14 >> /var/log/conzent-backup.log 2>&1

Use the absolute path to your install directory — cron does not inherit your shell's working directory.

A backup on the same disk as the database is not a backup. Add an offsite step:

15 3 * * * rsync -az /path/to/conzent/backups/ backup-host:/srv/conzent-backups/

Encrypt before it leaves if the destination is not yours: gpg --symmetric --cipher-algo AES256 backups/conzent-....tar.gz.

Restoring

bash scripts/restore.sh backups/conzent-20260722-030000.tar.gz --yes

--yes is mandatory — the restore replaces the current database outright. What it does, in order:

  1. Unpacks and validates the archive, printing its manifest.
  2. Stops the application containers, leaving MariaDB running.
  3. Imports the dump into the database named in your current .env.
  4. Restores the consent scripts volume.
  5. Starts everything back up.
  6. Runs migrations:migrate, so an older archive is brought up to the schema this version expects.
  7. Runs scripts:regenerate, so scripts are rebuilt against your current APP_URL, not the one in the archive.
  8. Flushes Redis.

Steps 6 and 7 are why a backup taken on old-domain.com restores cleanly onto an install now serving consent.example.com.

By default your existing .env is left alone. To take the archive's version too, add --restore-env; your previous file is kept as .env.before-restore-<timestamp>. Use that when rebuilding a lost server, not when rolling back data on a working one — the archived DB_PASSWORD will not match a freshly initialised database.

Rebuilding a server from scratch

# 1. Fresh install on the new machine
curl -sSL https://getconzent.com/install | sh -s -- --domain consent.example.com

# 2. Copy the archive across
scp backups/conzent-20260722-030000.tar.gz newhost:/root/conzent/

# 3. Restore data and configuration
cd /root/conzent
bash scripts/restore.sh conzent-20260722-030000.tar.gz --yes --restore-env

# 4. If the domain changed, set it and regenerate
sed -i 's|^APP_URL=.*|APP_URL=https://consent.example.com|' .env
docker compose up -d
docker compose exec app php bin/oci scripts:regenerate

Run a restore drill

An untested backup is a guess. Do this once, before you need it:

  1. Note a site name, its banner settings, and today's consent-log count.
  2. Take a backup.
  3. On a separate machine or VM, install fresh and restore the archive.
  4. Log in and confirm the site, banner configuration, and consent logs are all there.
  5. Load a page carrying that site's embed and check the banner still renders.

Ten minutes now, versus discovering the gap during an incident.

Before every upgrade

Updates preserve your database, but take a backup first anyway — a migration is the one thing a restore cannot undo:

bash scripts/backup.sh --keep 14 && bash scripts/install.sh --update

The full reference lives with the code: docs/backup-restore.md on GitHub.

Back to Documentation