Backup & upgrade
Upgrade
Section titled “Upgrade”git pulldocker compose build && docker compose up -d # migrations run on api boot, idempotentlyDatabase migrations run automatically when the API container boots, and are idempotent — pulling and bringing the stack back up is the whole upgrade.
Backup & restore
Section titled “Backup & restore”StoryOS persists two things — the Postgres database and the attachments
(the storyos_attachments volume, or your S3 bucket). They must be backed up and
restored as a matched pair: an attachment row in the database points at its
file by key, so a database from Monday with files from Tuesday is a corrupt
instance. The bundled scripts bind both halves to one timestamped recovery
point.
Back up
Section titled “Back up”./scripts/backup-restore/backup.sh # label = UTC timestamp./scripts/backup-restore/backup.sh nightly-2026-07-25 # or an explicit labelThis writes backups/<label>/ containing db.dump (pg_dump -Fc),
attachments.tgz, a counts.tsv row-count baseline, and a manifest.env with
checksums. No secrets are written — the database password stays inside the
postgres container.
Restore (rehearse into an isolated clone)
Section titled “Restore (rehearse into an isolated clone)”Restore never touches the live stack — it spins up a separate Compose project with its own volumes, rebuilds the database there, and runs the integrity checks:
./scripts/backup-restore/restore.sh backups/<label> # tears the clone down after./scripts/backup-restore/restore.sh backups/<label> --keep # leave it up to inspectRehearsing restores on a schedule is the point: an untested backup is a guess.
Restore into production (after a real loss)
Section titled “Restore into production (after a real loss)”docker compose up -d postgresdocker compose exec -T postgres pg_restore -U storyos -d storyos --clean --if-exists --no-owner < backups/<label>/db.dumpdocker run --rm -v storyos_storyos_attachments:/data -v "$PWD/backups/<label>":/in:ro \ alpine sh -c 'cd /data && tar xzf /in/attachments.tgz'docker compose up -d # api reconciles the schema on boot (migrations are idempotent)./scripts/backup-restore/verify-restore.sh storyos backups/<label>Integrity checks
Section titled “Integrity checks”Every restore asserts: auth loads (users/sessions present), row counts
match the source, relations resolve (no dangling links), and attachments
are present and openable. These run automatically inside restore.sh and can
be run any time with verify-restore.sh <project> backups/<label>. The exact
dump → restore → check path is exercised in CI, so the documented commands can’t
silently rot.
Guidance & larger deployments
Section titled “Guidance & larger deployments”Encrypt each backups/<label>/ before it leaves the host, keep off-host copies
(3-2-1), and alert both when a backup is overdue and when a scheduled restore
rehearsal fails. Deployments needing point-in-time recovery should run Postgres
with continuous WAL archiving (PITR) alongside these dumps.