# Backups and upgrades

> Back up both the database and the master key, and upgrade by changing the image tag.

Web page: https://infrainbox.app/docs/self-hosting/backup-and-upgrade/

## What to back up

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. |

> **Warning:** A database backup without the matching master key is not a usable backup. Restoring the dump alone brings up a stack that refuses to start over its own key check — which is the key doing its job, not a broken restore.

## Back up

From `deploy/compose/`, with the stack running:

```bash
./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.

## Restore

Onto a stopped or destroyed stack:

```bash
./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.

## Check it works

- `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.

## Upgrades

```bash
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.

## Rotating the master key

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:
   ```bash
   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.
