PostgreSQL backup with pg_dump
Goal: a regular, consistent dump of a PostgreSQL database running in Docker that survives a whole-server backup taken while running and can be restored even into a newer PostgreSQL version.
Prerequisites: shell access to the host (SSH), permission to run docker, a directory that is backed up further (USB drive, NAS, Umbrel backup).
Why a dump and not just a file backup
- A copy of the data directory is only reliable with the server shut down. A backup taken while running may contain the database in an inconsistent state. PostgreSQL docs
pg_dumptakes a consistent snapshot while running and doesn't block operation. Its output can be loaded into newer versions, so it doubles as insurance before an upgrade (17 → 18). PostgreSQL docs- Synthesis: keep the whole-server backups (e.g. umbrelOS) running and store the dump in a directory those backups include. The server backup then carries a consistent dump too.
Steps
- Find the container name, user and database:
docker ps --format '{{.Names}}' | grep -i postgres docker exec <container> psql -U <user> -l - Manual dump in custom format (compressed, restored with
pg_restore). PostgreSQL docspg_dumpruns inside the container, so the client version matches the server (Synthesis).docker exec <container> pg_dump -U <user> -Fc <database> > /path/to/backups/<database>-$(date +%F).dump - Optionally roles and other global settings.
pg_dumpdoesn't back them up. PostgreSQL docsdocker exec <container> pg_dumpall -U <user> --globals-only > /path/to/backups/globals-$(date +%F).sql - A script for regular runs that deletes old dumps (Synthesis), e.g.
pg-backup.sh:
The#!/bin/sh set -e CONTAINER=<container> DB=<database> DBUSER=<user> DIR=/path/to/backups KEEP_DAYS=14 TMP="$DIR/$DB-$(date +%F_%H%M).dump.part" docker exec "$CONTAINER" pg_dump -U "$DBUSER" -Fc "$DB" > "$TMP" mv "$TMP" "${TMP%.part}" find "$DIR" -name "$DB-*.dump" -mtime +"$KEEP_DAYS" -delete.partfile is renamed only after a successful dump, so an unfinished dump never looks like a valid backup. - Schedule it with cron, e.g. daily at 3:00, before the whole-server backup:
0 3 * * * /path/to/pg-backup.sh >> /path/to/backups/pg-backup.log 2>&1
Verification
- Dump contents (without restoring):
docker exec -i <container> pg_restore -l < file.dump | head - Test restore into a scratch database (Synthesis: a backup you've never restored isn't verified):
docker exec <container> createdb -U <user> -T template0 restore_test docker exec -i <container> pg_restore -U <user> -d restore_test < file.dump docker exec <container> dropdb -U <user> restore_testcreatedb -T template0andpg_restore -dper PostgreSQL docs.
Restore
- Into a new empty database:
createdb -T template0 <database>, thenpg_restore -d <database> file.dump(viadocker exec -ias above). PostgreSQL docs - When restoring into a fresh installation, load
globals-*.sql(roles) withpsqlfirst, then the database. - Synthesis: before restoring into the production database, stop the Node-RED flows that write to it.
Troubleshooting
pg_dumpasks for a password: pass it as an environment variable for that command only (docker exec -e PGPASSWORD=… …); never put it into the wiki or the repo. Synthesis.- Empty or tiny dump: wrong database or user; check step 1 and
pg-backup.log. - Restore complains about missing roles: restore
globals-*.sqlfirst.
Related
Sources
- PostgreSQL docs: SQL dump, custom format,
pg_restore,pg_dumpall --globals-only, file backup only with the server shut down. - Umbrel sources: apps as Docker Compose projects, data in
${APP_DATA_DIR}.
Intellihome