Backup and restore for self-hosted VeloCMS
How to set up automated PocketBase backups, store them off-site, and restore from a backup when something goes wrong.
Self-hosted VeloCMS stores all data in a PocketBase SQLite database and media files in a storage directory (or Cloudflare R2 if configured). A backup strategy should cover both. This guide assumes you're running VeloCMS via Docker Compose on a VPS.
What to back up
- PocketBase data directory — typically ./pb_data/ — contains the SQLite database and local media uploads.
- Environment file (.env) — contains your API keys and secrets. Store this separately in a secrets manager, not in the same backup.
- Custom themes directory — if you've created custom theme JSON files outside the default templates.
Automated daily backup with a cron job
PocketBase has a built-in backup API. You can call it with a cron job to create a timestamped backup automatically each night. Add this to your server's crontab (crontab -e):
# Daily backup at 03:00 UTC
0 3 * * * curl -X POST \
"http://localhost:8090/api/backups" \
-H "Authorization: Bearer $PB_SUPERUSER_TOKEN" \
>> /var/log/velocms-backup.log 2>&1Replace localhost:8090 with your PocketBase URL if it's on a different port. The backup is created in pb_data/backups/ as a .zip file containing the full database and media. Set PB_SUPERUSER_TOKEN to a long-lived superuser token from your PocketBase admin panel.
Shipping backups off-site
On-server backups don't help if the server fails. After each backup, copy the .zip to an off-site location. Cloudflare R2, AWS S3, or Backblaze B2 all work. Use rclone — a cross-platform CLI that supports all three providers — to sync the backups directory after each cron run. A 7-day retention with daily backups means you can restore to any point in the last week.
Restoring from a backup
To restore: (1) stop the VeloCMS containers (docker compose down), (2) download the backup .zip from your off-site storage, (3) unzip it into your pb_data/ directory — overwriting the existing files, (4) restart containers (docker compose up -d). PocketBase reads the restored database on startup. Verify restoration by logging into /admin and checking that your content is present.
Restoring a backup overwrites your current database permanently. There is no undo. If you're unsure, rename the existing pb_data/ to pb_data_old/ before restoring — this gives you a fallback if the restore fails.
Testing your backup process
A backup you've never tested is not a backup — it's hope. Every 90 days, restore a backup to a local environment and verify that the site loads, posts are present, and media serves correctly. If the restoration fails, the problem is in your backup procedure, not in a crisis.