Skip to content

Backup & restore

Backup and restore are server-side only, deliberately. This action both exfiltrates (backup) and can completely overwrite (restore) every engagement’s data, so it’s a terminal-only operation for whoever already has that level of server access, not a button reachable over HTTP. There is no web UI at all; everything on this page runs via docker compose exec.

This is one-time setup. create_backup writes into BACKUP_DIR (/app/backups inside the container by default), which needs a real host directory behind it to survive a container recreation:

Terminal window
mkdir -p backups

Set BACKUP_ENCRYPTION_PASSPHRASE in .env. It’s required for unattended runs, since there’s no one at a terminal to prompt. Then run this daily via the host’s own cron:

Terminal window
0 2 * * * cd /path/to/redscribe && docker compose exec -T web python manage.py create_backup

Each run writes one timestamped, encrypted file (redscribe-backup-<timestamp>.rsbk) into BACKUP_DIR and prunes anything older than BACKUP_RETENTION_DAYS (default 30, a month of daily backups). --passphrase overrides BACKUP_ENCRYPTION_PASSPHRASE for a single run if you ever need that.

Terminal window
docker compose exec web python manage.py restore_backup /app/backups/redscribe-backup-<timestamp>.rsbk

(Prompts for the passphrase interactively; --passphrase skips that for scripted disaster-recovery drills.)

Before touching the live database, restore_backup loads the backup into a throwaway scratch database and compares its accounts against the ones live right now, then prints exactly what would change. This is the direct answer to a specific problem. A whole-database restore silently undoes any account change made after the backup was taken, including someone who was deactivated since (say they left the team) quietly regaining access, in a way that’s easy to miss if you’re not specifically looking for it.

You’ll see one of four things per affected account:

Outcome Meaning
Reactivated Inactive right now, active in the backup. This is the one that matters most: if this account was deliberately deactivated since the backup, restoring undoes that.
Newly deactivated The reverse, reactivated since the backup.
Disappearing Created after the backup, so restoring removes it.
Reappearing Deleted since the backup, so restoring brings it back.

Nothing here is automatic. You still have to look at the list and decide, but it can no longer slip past unnoticed. Type restore at the prompt to proceed, anything else to abort with zero changes made. --yes skips the prompt (the report is still computed and printed either way) for scripted DR, so make sure you know what you’re restoring before you automate past this step.

Immediately before the actual pg_restore (which drops every table first, then restores, so it isn’t something a partial failure can be undone from on its own), restore_backup also takes its own encrypted snapshot of whatever’s live right now and writes it into BACKUP_DIR as redscribe-pre-restore-safety-<timestamp>.rsbk, using the same passphrase.

Because this loads the backup up to three times (the scratch-database comparison, the pre-restore safety snapshot, and the real restore), expect it to take roughly three times as long as a raw pg_restore would.

Upgrading, and rolling back a bad deploy or migration

Section titled “Upgrading, and rolling back a bad deploy or migration”

entrypoint.sh runs python manage.py migrate --noinput unconditionally on every container start, and there’s no dry-run step and no automatic snapshot before it touches the schema. Taking a manual snapshot before every upgrade is the way back if something goes wrong; see Upgrading & rolling back for the exact step-by-step sequence (snapshot → pull/build → verify → roll back code and/or restore the snapshot if needed).