Skip to content

Backup and Restore

Strawly stores all state in PostgreSQL: user accounts, roles, inventory data, recommendations, initiatives, scopes, and cloud provider credentials. No state lives inside Docker containers or volumes outside the database. A pg_dump of the strawly database is a complete backup.

What to back up

Two things are required to restore a working Strawly instance:

  1. The database dump — produced by pg_dump, contains all rows.
  2. CREDENTIALS_ENCRYPTION_KEY from .env — cloud provider credentials stored in the database are encrypted with this key. A dump restored without the matching key leaves those credentials unreadable.

Back up your .env file alongside each database dump. That covers both requirements.

Dump files are sensitive

The dump contains user data and configuration. Store backups in a location with restricted access and do not commit them to version control.

Manual backup

Run pg_dump inside the running strawly-postgres container:

docker exec strawly-postgres \
  pg_dump -U strawly -d strawly -Fc \
  > ~/strawly-backups/strawly_$(date +%Y%m%d_%H%M%S).dump

The -Fc flag produces a compressed binary format that pg_restore can use. Create the output directory first if it does not exist:

mkdir -p ~/strawly-backups

Scheduled backup

Add a cron job on the host to run the backup automatically. Open the crontab:

crontab -e

Add a line to back up daily at 02:00:

0 2 * * * mkdir -p ~/strawly-backups && docker exec strawly-postgres pg_dump -U strawly -d strawly -Fc > ~/strawly-backups/strawly_$(date +\%Y\%m\%d_\%H\%M\%S).dump 2>> ~/strawly-backups/backup.log

The % characters must be escaped as \% inside crontab entries.

To keep only the last 30 days of backups, add a second cron entry:

30 2 * * * find ~/strawly-backups -name "strawly_*.dump" -mtime +30 -delete

Restore

Restore replaces all data

The restore procedure drops and recreates all database objects. Any data written after the backup was taken will be lost.

Stop the backend before restoring to prevent writes during the operation:

docker compose -f docker-compose.generated.yml stop backend

Copy the dump file into the container and restore:

docker cp ~/strawly-backups/strawly_20260101_020000.dump strawly-postgres:/tmp/restore.dump

docker exec strawly-postgres \
  pg_restore -U strawly -d strawly --clean --if-exists /tmp/restore.dump

docker exec strawly-postgres rm /tmp/restore.dump

Start the backend again:

docker compose -f docker-compose.generated.yml start backend

If the backup was taken from a different environment (different CREDENTIALS_ENCRYPTION_KEY), update that value in .env to match the key used when the backup was made, then recreate the backend:

docker compose -f docker-compose.generated.yml up -d --force-recreate backend

Verify the restore

Confirm the database contains data after restoring:

docker exec strawly-postgres \
  psql -U strawly -d strawly \
  -c "SELECT schemaname, tablename, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC LIMIT 10;"

The output shows live row counts per table. A successful restore shows non-zero counts in the core tables (users, roles, inventory, recommendations, and others).