Skip to content

Backups and upgrades

Two things, kept apart from each other:

What Why
The PostgreSQL database Every event, incident, source, destination and setting.
The infrainbox-data volume The master encryption key. Without it, the encrypted secrets in a database dump — destination credentials, push keys — can’t be read back.

From deploy/compose/, with the stack running:

Terminal window
./backup.sh -o /srv/backups/infrainbox-$(date +%F)

This writes two files into that directory: infrainbox.dump (a pg_dump -Fc of the database) and infrainbox-data.tar.gz (the volume holding the master key). It runs against whatever compose project and .env the stack was started with, so a Caddy override or a custom project name still applies.

Onto a stopped or destroyed stack:

Terminal window
./restore.sh -o /srv/backups/infrainbox-2026-09-14

In order, this starts PostgreSQL, applies the running version’s migrations, restores the infrainbox-data volume from the tarball, restores the database with pg_restore --clean --if-exists, then starts the stack again. Two flags for partial restores:

  • --db-only — restore the database only; use this when the volume is already intact or already restored.
  • --no-start — stop after restoring, without starting the stack.

Restore onto the same version you backed up from. Migrations only ever move forward, so restoring an old dump onto a newer binary applies migrations it never saw before restoring.

  • restore.sh finishes without error and docker compose ps shows both services healthy.
  • Signing in shows the incidents and sources you had before.
  • A destination’s “Send test” button still works — proof its stored credential decrypted correctly under the restored master key.

Test this restore procedure at least once before you need it for real.

Terminal window
docker compose pull # or set a new tag directly
docker compose up -d

Migrations run automatically at start, and the server refuses to start against a database schema newer than it knows how to handle — so a downgrade is caught immediately rather than corrupting data. Never pin to :latest: an unpinned tag turns an ordinary restart into an unplanned upgrade. Back up before an upgrade that touches the database schema, the same as before any other change to a production instance.

If you need to rotate the master key (routine hygiene, or suspected exposure):

  1. Set the new key as INFRAINBOX_MASTER_KEY (or _FILE) and the current one as INFRAINBOX_MASTER_KEY_PREVIOUS, then restart every server process. It now opens existing secrets under either key and seals new ones under the new one.
  2. Run the rotation itself, which re-wraps every stored secret in small, resumable batches:
    Terminal window
    docker compose exec infrainbox /infrainbox admin keys rotate
  3. Back up the new key (infrainbox admin master-key export, kept apart from database dumps as usual), then remove INFRAINBOX_MASTER_KEY_PREVIOUS and restart. The server refuses to start without the previous key while any secret still needs it, so this is safe to attempt and re-run.

infrainbox admin master-key export is the only place the key is ever shown — never in the dashboard, never in a log.