Skip to main content

Database backup

Before every upgrade, take a custom-format PostgreSQL dump and encrypt it with a separately retained age recipient. Keep passwords, dumps and decryption keys outside Git. From the image bundle directory:
Decrypt and restore into an isolated empty PostgreSQL 18 instance using pg_restore --exit-on-error --no-owner --no-acl. Compare table counts, migration version, case/warning/receipt identifiers and event/outbox JSON object types. Read cases from the restored instance. A dump is not a verified backup until the restore rehearsal succeeds. Never attach the live volume to it.

When private-object storage is enabled

If PUP_OBJECT_STORAGE_ENABLED=true, a database dump alone is incomplete. PostgreSQL stores object records and encrypted staging data; retained attachment, evidence, transcript, and export bytes can live in the separate private Azure Blob store.
  • Protect the private containers alongside PostgreSQL. Retain the object keys, bytes, content types, metadata, and provider versions needed by the database records. Keep backups private and preserve them until explicitly deleted. Continue to honor legal holds.
  • Keep an encrypted recovery copy of the storage configuration and access needed to read those containers, including any configured encryption scope. Preserve the complete PUP_OBJECT_STAGING_KEYS keyring used by the API, bot, and worker, with its key IDs. Retained staging rows require their original key; generating a new key with the same ID does not decrypt them. Retain older keys while any retained database backup or staging row needs them, regardless of age. Completion does not remove a retained recovery copy; retire a key only after all copies that need it have been explicitly deleted.
  • Rehearse with a separate database and private storage copy, with live Discord delivery disabled. Check that an authorized case-evidence read retrieves the expected bytes and passes integrity checks. If the backup contains queued staging, verify that its matching key can decrypt it and that storage work can complete against the rehearsal store.
  • Rehearse private-object recovery on a disposable test object using an installation operator account with the required server permission. Wait for the queued restore result and verify the object can be read; an accepted restore request alone does not establish recovery. Never restore explicitly deleted member data merely to make a backup test pass.
If private-object storage has never been enabled and no retained object records depend on it, the database-only rehearsal above is sufficient.

Upgrade

Retain the previous verified release manifest, all four deployed image digests, their source version, and configuration. Review schema compatibility and Discord application identity. Follow Self-host Pup to obtain the next version’s portable images: schema-2 releases supply three ARM64 runtime images and a Cloudflare dashboard artifact, so the Compose dashboard must be built from matching source. Other architectures require matching source builds for all four services. Do not combine a newer runtime with an older dashboard. For migration 0142_remove_automatic_retention, take and verify a backup, then stop the old API, bot and worker before running the migration. This prevents an already claimed deletion job from completing during the upgrade. Start only the new binaries afterward; older binaries are incompatible with the removed fields and workers. The migration preserves stored content and cancels old automatic deletion work. It cannot recover content that was already erased. Update the four image digest values in config.env, run the one-shot migration service, recreate the application containers, and verify Connection status, Cases, and Discord delivery. Recheck private-object access when enabled.

Rollback

Migrations are forward-only. Restore earlier application digests only when they support the current schema. Otherwise restore into a separate database, verify it, and deliberately switch connections. Do not delete the live volume or overwrite immutable release identities during recovery. Documentation or progress-publication failure is separate from application health. Retry the same accepted publication without undoing a healthy deployment.