Skip to content

Backup and Restore

This guide covers backing up and restoring the Patrimoine SQLite database, including automated backups and offsite sync.


The backend creates a SQLite backup on startup and every 24h using Python’s sqlite3.backup() (WAL-safe). Backups are saved to ./data/backups/patrimoine_<timestamp>.db (a backups/ directory next to the database file) with chmod 600 permissions.

This feature is controlled by the AUTO_BACKUP environment variable:

Value Behavior
true Backup runs on startup + every 24h
false (default) Backup task is not created — no CPU, no disk I/O, no lock contention
backend/container/.env
AUTO_BACKUP=true

Opt-in: set AUTO_BACKUP=true to enable it. Set it back to false if the backup causes database lock contention (e.g., when multiple processes access the database simultaneously). The external backup-db.sh script (below) is not affected by this setting.

The internal backup is never pruned: delete old files in data/backups/ yourself, or rely on backup-db.sh, which keeps the last 30. With the arq worker the backup runs at 03:00 instead of at startup. It does nothing on PostgreSQL.


The sqlite3 .backup command is WAL-safe — it does not interrupt the running service and produces a consistent snapshot.

Terminal window
# Create a backup inside the container
podman compose exec backend sqlite3 /data/patrimoine.db ".backup '/data/patrimoine-backup-$(date +%Y%m%d).db'"
# Copy the backup to the host
cp ./data/patrimoine-backup-$(date +%Y%m%d).db ./backups/

Alternative method: stop the backend, copy the file, restart. This causes a brief service interruption but guarantees a quiescent database.

Terminal window
podman compose stop backend
cp ./data/patrimoine.db ./backups/backup.db
podman compose start backend

Terminal window
podman compose stop backend
cp ./backups/backup.db ./data/patrimoine.db
podman compose start backend

The backup-db.sh script automates SQLite backups with rotation and optional offsite sync.

Terminal window
./backup-db.sh
Terminal window
# Daily backup at 03:00
0 3 * * * cd /path/to/patrimoine && ./backup-db.sh
  1. Sets umask 077 — backup files are created with 600 permissions (owner read/write only).
  2. Online backup — uses sqlite3 .backup with single-quote escaping in the path to prevent shell injection.
  3. chmod 600 on the backup file.
  4. Rotation — keeps the last 30 backups, deletes older ones.
  5. Offsite sync (optional) — uploads to WebDAV or S3 if configured.
Feature Implementation
Shell #!/bin/sh (POSIX)
Error handling set -eu
File permissions umask 077 + chmod 600
Path escaping Single quotes escaped via sed "s/'/''/g"
Rotation ls -t ... | tail -n +31 | xargs rm -f (keeps last 30)
Backup dir First argument (./backup-db.sh /path/to/dir); default ./data/backups
DB path DATABASE_PATH environment variable; default ./data/patrimoine.db

The script supports two offsite sync methods, enabled via environment variables.

These variables are read only by backup-db.sh, from the environment of the host shell or cron job that runs it.

Export them in the shell, set them inline in the crontab line, or source a private file of your own from a small wrapper script:

Terminal window
# crontab: daily at 03:00 with WebDAV sync
0 3 * * * cd /path/to/patrimoine && WEBDAV_URL=https://cloud.exemple.fr/remote.php/dav/files/user/backups/ WEBDAV_USER=user WEBDAV_PASSWORD=secret ./backup-db.sh
Terminal window
# host environment of the backup-db.sh run
WEBDAV_URL=https://cloud.exemple.fr/remote.php/dav/files/user/backups/
WEBDAV_USER=user
WEBDAV_PASSWORD=secret

The script uploads via curl using a temporary .netrc file:

  1. Creates a temp file with mktemp.
  2. Writes machine, login, and password entries.
  3. Sets chmod 600 on the temp file.
  4. Uploads with curl --netrc-file.
  5. Deletes the temp file after upload.

The password is never visible in ps output (it is read from the .netrc file, not passed as a command-line argument).

Terminal window
# host environment of the backup-db.sh run
S3_BUCKET=mon-bucket-patrimoine

Requires the aws CLI to be installed and configured on the host. The script uploads with aws s3 cp.

WebDAV and S3 are independent — you can configure one, both, or neither. If no variables are set, the script performs only the local backup with rotation.


If using PostgreSQL instead of SQLite (see PostgreSQL Deployment), use pg_dump instead of sqlite3 .backup:

Terminal window
docker compose -f docker-compose.yml -f docker-compose.postgres.yml exec postgres \
pg_dump -U patrimoine patrimoine > backup.sql

With podman.sh: podman exec patrimoine-postgres pg_dump -U <user> <db> > backup.sql (user and database come from DATABASE_URL).

The backup-db.sh script is SQLite-only.

To move data from an existing SQLite deployment to a new PostgreSQL one, use the app’s own JSON backup export/import (the Export and Import pages) rather than pg_dump/.backup — see PostgreSQL Deployment: Migrating an Existing SQLite Deployment to PostgreSQL.


Besides database-level backups, every user can download a per-account JSON backup from the Export page (GET /api/v1/export/backup) and restore it from the Import page (POST /api/v1/import/backup). The file carries a version (currently 15), and a backup written by an older version restores into a newer one. This backup covers one user’s data only and is not a substitute for a database backup. See the User Manual’s Import & Export page.