NestFleetDocs

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_dump against the running PostgreSQL container using docker 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:

  1. Stop all services except the database:
    docker compose -f docker-compose.prod.yml stop api worker console
  2. Decompress the backup file:
    gunzip backups/nestfleet_20260401_020000.sql.gz
  3. 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
  4. Restart all services:
    docker compose -f docker-compose.prod.yml up -d
  5. 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.