Instance backup and restore: one archive of database and media, and a documented way back #32

Closed
opened 2026-09-05 14:04:13 +00:00 by tiagoagueda · 0 comments
Owner

Why

What exists is per person: export_data and import_data (core/management/commands/), and the Export everything page. An operator has nothing at instance level except copying the data directory, and with SQLite that is not safe while the application is running unless the copy is a proper backup. By default the database file (data/postulo.sqlite3, base.py line 92) and the media (data/media, MEDIA_ROOT, line 168) share one directory — one Compose volume — which makes a coherent archive easy, provided something writes it correctly.

Shape

  • manage.py backup <target> writes one archive containing:
    • the database — SQLite through the sqlite3 backup API (connection.backup()), which is consistent while the application runs, and PostgreSQL through pg_dump when it is on the path, refusing with a clear message when it is not;
    • the media directory, streamed file by file (tar, not an in-memory zip: #28 makes media large);
    • manifest.json: Postulo version, format version, database engine, created-at, row counts per model — enough for restore to refuse a mismatch politely.
  • manage.py restore <archive> onto an empty instance only (or --force, which says what it will destroy), checks the manifest against the installed version, restores database then media, then runs migrations so an older backup lands on a newer Postulo.
  • Compose documentation: the one-liner, a cron example, a retention suggestion, and the note that the archive holds everyone's data on the instance and belongs on encrypted storage — encryption itself is the backup tool's job (restic, borg, age), not Postulo's.
  • Server settings (#24) → Overview: Back up now, the age of the newest backup in the configured directory, and a warning when it is older than a week.
  • The image gets a backups/ directory under /app/data and the entrypoint mentions it in the logs on first run, because the first time anyone thinks about backups is after they needed one.

Classification

Enhancement. Not breaking: two commands and a page section.

Open questions

  1. Should backup also produce the per-person exports inside the archive, so a single person can be restored without the whole instance? Proposal: no; export_data exists for that and the archive would double in size.
  2. Verify the archive after writing (open it, read the manifest, checksum the database)? Proposal: yes, always; a backup that was never opened is a hope.
## Why What exists is per person: `export_data` and `import_data` (`core/management/commands/`), and the *Export everything* page. An operator has nothing at instance level except copying the data directory, and with SQLite that is not safe while the application is running unless the copy is a proper backup. By default the database file (`data/postulo.sqlite3`, `base.py` line 92) and the media (`data/media`, `MEDIA_ROOT`, line 168) share one directory — one Compose volume — which makes a coherent archive easy, provided something writes it correctly. ## Shape - **`manage.py backup <target>`** writes one archive containing: - the database — SQLite through the `sqlite3` **backup API** (`connection.backup()`), which is consistent while the application runs, and PostgreSQL through `pg_dump` when it is on the path, refusing with a clear message when it is not; - the media directory, streamed file by file (tar, not an in-memory zip: #28 makes media large); - `manifest.json`: Postulo version, format version, database engine, created-at, row counts per model — enough for `restore` to refuse a mismatch politely. - **`manage.py restore <archive>`** onto an empty instance only (or `--force`, which says what it will destroy), checks the manifest against the installed version, restores database then media, then runs migrations so an older backup lands on a newer Postulo. - **Compose documentation**: the one-liner, a cron example, a retention suggestion, and the note that the archive holds *everyone's* data on the instance and belongs on encrypted storage — encryption itself is the backup tool's job (restic, borg, age), not Postulo's. - **Server settings** (#24) → Overview: *Back up now*, the age of the newest backup in the configured directory, and a warning when it is older than a week. - **The image** gets a `backups/` directory under `/app/data` and the entrypoint mentions it in the logs on first run, because the first time anyone thinks about backups is after they needed one. ## Classification Enhancement. Not breaking: two commands and a page section. ## Open questions 1. Should `backup` also produce the per-person exports inside the archive, so a single person can be restored without the whole instance? Proposal: no; `export_data` exists for that and the archive would double in size. 2. Verify the archive after writing (open it, read the manifest, checksum the database)? Proposal: yes, always; a backup that was never opened is a hope.
tiagoagueda added this to the 0.2.0 milestone 2026-09-05 14:04:13 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#32
No description provided.