Backup & Restore
NestFleet stores all state in PostgreSQL. A full backup consists of a compressed database dump. The included scripts/backup.sh script automates this and rotates old backups automatically.
All NestFleet data lives in the database — cases, knowledge base articles, change requests, audit logs, and encrypted secrets. There are no files on disk that need to be backed up separately.
The backup script
scripts/backup.sh does the following:
- Runs
pg_dumpagainst the running PostgreSQL container usingdocker compose exec - Compresses the dump with gzip (typically 5–20x compression for text-heavy data)
- Names the file with a timestamp:
backups/nestfleet_YYYYMMDD_HHMMSS.sql.gz - Rotates the backup directory to keep only the most recent 7 backups, deleting older ones automatically
- Exits with a non-zero status code on failure, making it safe to use in cron with error logging
Backup location
Backups are written to the backups/ directory at the root of the repository. This directory is listed in .gitignore and will never be committed to version control.
For off-site durability, sync this directory to object storage (S3, Backblaze B2, Hetzner Object Storage) after each backup run. A simple approach is to pipe the script output into rclone or use a cron job that runs rclone sync backups/ remote:bucket/nestfleet-backups/ after the dump.
Run a backup manually
bash scripts/backup.sh
The script must be run from the root of the NestFleet repository directory, where the docker-compose.prod.yml file is located. It reads the POSTGRES_PASSWORD from your.env file automatically.
Automate with cron
Add a cron entry to run backups nightly at 02:00 local time. Edit your crontab with crontab -e and add:
0 2 * * * /path/to/nestfleet/scripts/backup.sh >> /var/log/nestfleet-backup.log 2>&1
Replace /path/to/nestfleet with the absolute path to your cloned repository. The script output (including errors) is appended to the log file. Monitor that file or configure a log rotation policy with logrotate to prevent it from growing unbounded.
Test that the cron job runs as expected by running it manually first with the full path, under the same user account that cron uses. Cron jobs run with a minimal PATH, so using absolute paths is critical — the script handles this internally.
Restore procedure
Stop the API and worker before restoring. Restoring while the API is running can result in partial writes, foreign key violations, and pg-boss queue corruption. Take the stack down first, restore, then bring it back up.
Follow these steps to restore from a backup:
- Stop all services except the database:
docker compose -f docker-compose.prod.yml stop api worker console
- Decompress the backup file:
gunzip backups/nestfleet_20260401_020000.sql.gz
- Drop and recreate the database schema, then restore:
# Drop and recreate the database docker compose -f docker-compose.prod.yml exec postgres psql -U postgres -c "DROP DATABASE nestfleet; CREATE DATABASE nestfleet;" # Restore from the dump docker compose -f docker-compose.prod.yml exec -T postgres psql -U postgres nestfleet < backups/nestfleet_20260401_020000.sql
- Restart all services:
docker compose -f docker-compose.prod.yml up -d
- Verify health:
curl https://your-domain/health
Verifying a backup without restoring
To verify a backup is readable without touching the production database, restore it into a temporary container:
docker run --rm -e POSTGRES_PASSWORD=test -d --name pg-verify postgres:16 gunzip -c backups/nestfleet_20260401_020000.sql.gz | docker exec -i pg-verify psql -U postgres docker stop pg-verify
If the restore completes without errors, the backup is valid. Run this check monthly or after any significant data change.