Tournevia

Backup and restore

Back up the database, keep encrypted copies off the host, restore into an isolated copy and switch to it.

1. Before you start

Run the commands from the kit matching the database schema (normally the installed version), with Python 3.11+ and the packages from backend/requirements.txt. The installation's Compose postgres service must be running: the helper runs PostgreSQL's own tools inside that container. On macOS/Linux, use python3. On a Linux server, a virtual environment keeps these packages apart from the system: see Install on a VPS, step by step, where a backup and an isolated restore were run.

For a launcher-created installation, add --workspace PRIVATE_PATH before the command, as below. For a manual Compose installation configured in .env, omit it.

Passwords travel in the child process environment, never on the command line. PostgreSQL errors and dumps are never printed, because they can contain record values.

2. Take a backup

python scripts/database_backup.py --workspace PRIVATE_PATH backup

Without --output, the archive is named vrp-<UTC time>-<id>.dump in the kit's backups folder. With --output FILE, choose a new name each time: an existing file is never overwritten.

The command first checks that the database schema matches the kit. It writes a consistent PostgreSQL custom archive and publishes it only when complete. The archive contains accounts with their password hashes, the company and its settings, depots and places, customers, vehicles, deliveries, calculations and saved plans; login sessions are excluded.

On Windows, the archive inherits its folder's access rights: use a folder that only operators can read.

3. Encrypt archives that leave the host

Install age on the host and pass a public recipient:

python scripts/database_backup.py --workspace PRIVATE_PATH backup --encrypt-to age1...

The verified archive is encrypted into a .dump.age file and the unencrypted temporary file is removed. Only the matching private identity can restore it: keep that identity away from the server.

4. Schedule, copy off the host and prune

  1. Schedule the backup command with cron, a systemd timer or the Windows Task Scheduler.
  2. Copy each encrypted archive to separate storage, for example with rclone or rsync. A copy on the same disk does not protect against losing the host.
  3. Remove older archives explicitly:
python scripts/database_backup.py prune --keep 14

prune keeps the newest N archives in the kit's backups folder (--directory selects another one). It deletes only files named by this helper (vrp-…dump or vrp-…dump.age); other files, including the launcher's before-upgrade-*.dump archives, are left alone. Nothing is ever pruned automatically.

The helper exits with a non-zero status on any failure and then publishes no archive. Alert on that status and on the age of the newest archive, so that a schedule that silently stopped is noticed. Agree how long archives are kept before entering real data: backups keep deleted or erased records until they are pruned.

5. Restore into an isolated database

python scripts/database_backup.py --workspace PRIVATE_PATH restore backups/ARCHIVE.dump

For an encrypted archive, add --identity IDENTITY_FILE. Restore only archives you trust: a PostgreSQL archive can contain executable SQL.

The command creates a new database named vrp_restore_ followed by 32 hexadecimal digits, restores in one transaction, checks the schema, clears login sessions and reapplies the application's permissions. It prints the database name and row counts per table. It never replaces the live database, changes the configuration, starts a worker or switches users. If it fails after creating the database, that isolated database is kept for inspection.

The row counts are a first check only.

6. Switch to the restored copy

For a launcher-created installation, use its launcher:

python scripts/start_server.py --directory PRIVATE_PATH --cutover-to vrp_restore_...

A local installation uses start_local.py with the same options. A manual Compose installation uses the helper, listing the additional Compose files it runs with, in order; the base deploy/docker-compose.example.yml always comes first:

python scripts/database_backup.py cutover vrp_restore_... --overlay deploy/docker-compose.routing.yml

The cutover checks that the target exists and matches the application's schema, then stops the API and worker so neither database is written during the switch. It points POSTGRES_DB (and DB_NAME, when set) at the target and starts the installation: setup reapplies migrations and permissions, and the HTTPS proxy is restarted. If the installation does not become healthy, the previous database is put back and started again.

No database is changed or removed. Users sign in again. Check named sign-in, saved plans and a new calculation before relying on the copy.

7. Switch back

The cutover prints the exact command to switch back to the previous database. It is the same cutover, naming the previous database. Keep the previous database until the checks of step 6 pass.

The recovery point is the newest archive: choose the backup interval from how much planning work you can afford to lose.

8. Retention and erasure

Plans and calculations keep copies of delivery addresses, instructions and coordinates. The API's database role cannot delete them; these commands use the migration role, in the setup container. The kit documents them for a Compose installation configured in .env:

docker compose --env-file .env -f deploy/docker-compose.example.yml run --rm setup python -m app.retention purge --older-than-days 90 --dry-run
docker compose --env-file .env -f deploy/docker-compose.example.yml run --rm setup python -m app.retention erase-order --external-id ORDER-123
  • purge --older-than-days N (at least 1) deletes plans not updated for N days and calculations that finished more than N days ago; a plan that a calculation is still working on is kept. --orders also deletes deliveries dated more than N days ago.
  • erase-order --external-id ID replaces one delivery's address with [erased], clears its instructions and coordinates, deactivates it and deletes the plans and calculations that copied it.

Both print counts only and accept --dry-run to report without changing anything. They never delete a waiting or running calculation; erase-order refuses while one includes the delivery. Earlier backups keep the data until they are pruned, so set the backup retention within the agreed period.

On this page