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

Steps

  1. Find the container name, user and database:
    docker ps --format '{{.Names}}' | grep -i postgres
    docker exec <container> psql -U <user> -l
    
  2. Manual dump in custom format (compressed, restored with pg_restore). PostgreSQL docs pg_dump runs 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
    
  3. Optionally roles and other global settings. pg_dump doesn't back them up. PostgreSQL docs
    docker exec <container> pg_dumpall -U <user> --globals-only > /path/to/backups/globals-$(date +%F).sql
    
  4. A script for regular runs that deletes old dumps (Synthesis), e.g. pg-backup.sh:
    #!/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
    
    The .part file is renamed only after a successful dump, so an unfinished dump never looks like a valid backup.
  5. 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

Restore

Troubleshooting

Related

Sources