This page documents the backup solution deployed on each client VPS to protect the practice's PostgreSQL database, configuration files, and SSH tunnel keys.
Each client VPS runs a daily backup via cron, managed by a Python script. Backups are stored locally on the client VPS in /var/backups/practice/. No off-site transfer is configured by default — backups remain on the same VPS for quick restoration.
| Attribute | Value |
|---|---|
| Schedule | Daily at 03:00 UTC |
| Retention | 14 days (configurable) |
| Storage | Local disk (/var/backups/practice/daily/) |
| Trigger | Cron (/etc/cron.d/practice-backup) |
| Auth model | Peer auth via sudo -u postgres (no passwords stored) |
| Item | Source | Format |
|---|---|---|
| PostgreSQL database | pg_dump --clean --if-exists --no-owner --no-privileges |
Gzip-compressed SQL (practice-YYYY-MM-DD.sql.gz) |
| PostgreSQL config | /etc/postgresql/16/main/postgresql.conf, pg_hba.conf, pg_ident.conf |
Tar archive (practice-YYYY-MM-DD-config.tar.gz) |
| SSH tunnel keys | ~clientadmin/.ssh/tunnel_key, tunnel_key.pub |
Included in config archive |
| Package manifest | dpkg -l |
packages.txt inside config archive |
| Integrity manifest | SHA256 of all backup files | practice-YYYY-MM-DD-manifest.json |
/var/backups/practice/
├── latest -> daily/ # Symlink to most recent backup
├── daily/
│ ├── practice-2026-06-10.sql.gz # Database dump
│ ├── practice-2026-06-10-config.tar.gz # Config files + SSH keys
│ └── practice-2026-06-10-manifest.json # Checksums
└── logs/
└── backup.log # Script logging
/opt/backup-client.pyThe main backup script. Idempotent — safe to run manually at any time.
# Run manually (same as cron trigger):
sudo /opt/backup-client.py
What it does:
pg_dump via sudo -u postgres (local peer auth) to dump the practice databaselatest symlink/opt/restore-client.pyCompanion restore script. Takes a backup date and restores the database and config files.
# Restore the latest backup:
sudo /opt/restore-client.py latest
# Restore a specific date:
sudo /opt/restore-client.py 2026-06-10
# Restore to a different database name:
sudo /opt/restore-client.py latest my_app_db
Restore process:
psql/etc/practice-backup.confOptional INI-style config file. If absent, defaults are used.
[database]
# Name of the PostgreSQL database to back up
name = practice_app_db
# PostgreSQL system user (uses local peer auth via sudo)
pg_user = postgres
[backup]
# Number of daily backups to retain
retention_days = 14
# Base backup directory
base_dir = /var/backups/practice
/etc/cron.d/practice-backup# Practice Client VPS Backup — daily at 3:00 AM
0 3 * * * root /opt/backup-client.py
Add these steps to the Client VPS provisioning process (between steps 2 and 3 of the main setup, or after step 5):
mkdir -p /var/backups/practice/{daily,logs}
Copy the following files from the principal VPS or provisioning source:
/opt/backup-client.py — backup script/opt/restore-client.py — restore script/etc/practice-backup.conf — configuration (optional)/etc/cron.d/practice-backup — cron trigger# Make scripts executable
chmod 755 /opt/backup-client.py /opt/restore-client.py
chmod 644 /etc/practice-backup.conf /etc/cron.d/practice-backup
# Run the backup manually
sudo /opt/backup-client.py
# Check backup files were created
ls -la /var/backups/practice/daily/
# Verify the manifest
cat /var/backups/practice/daily/practice-$(date +%F)-manifest.json
# Check the log
tail /var/log/practice-backup.log
systemctl status cron
In the event of data loss or corruption:
# 1. Restore the latest backup
sudo /opt/restore-client.py latest
# 2. If the restore script is unavailable, do it manually:
zcat /var/backups/practice/daily/practice-YYYY-MM-DD.sql.gz | \
sudo -u postgres psql -d practice_app_db
# 3. Restart the Django application (triggers new connections)
# (SSH into principal VPS and restart the container)
ssh admin@88.208.212.211 'docker restart gp_booking_app'
| Limitation | Notes |
|---|---|
| Local-only storage | Backups are on the same VPS. A disk failure loses both data and backups. Consider adding off-site transfer (rsync/SCP to principal VPS or S3). |
| No encryption at rest | Backup files are unencrypted on disk. If this is a concern, add GPG encryption to the script. |
| No monitoring | Backup failures are logged but not alerted. Consider adding a cron health check or email notification. |
| Principal VPS data not covered | Organisation records, integrations, and platform staff live on the principal VPS and need a separate backup strategy. |
| Date | Change |
|---|---|
| 2026-06-10 | Initial backup solution: Python script, cron trigger, local storage, 14-day retention |