Skip to content

§ Guides · postgresql

How to back up PostgreSQL running in Docker (and actually restore it)

pg_dump and pg_dumpall from a running container, a restore into a scratch container to prove the backup works, and the mistakes that corrupt dumps.

2 min readXtic team

A PostgreSQL backup is two things: a dump of each database, and the roles that own them. This guide takes both from a database running in Docker, restores them into a throw-away container, and checks that the data came back. The commands use a container named db and a database named appdb; replace them with yours.

Do not copy the data directory of a running server

Copying the volume behind /var/lib/postgresql/data while PostgreSQL runs gives you files from different moments — the copy may not start, or may start with silent damage. Use PostgreSQL’s own tools, which read a consistent snapshot while the database keeps serving traffic.

1. Dump each database

The custom format (-Fc) is compressed, restorable selectively and readable by pg_restore:

Terminal window
docker exec db pg_dump -U postgres -Fc --lock-wait-timeout=120s appdb > appdb.dump

Do not add -t to docker exec: a pseudo-terminal rewrites line endings and corrupts binary output. pg_dump takes only lightweight locks, but it waits for (and then blocks) schema changes, which is why the lock timeout is there.

2. Dump the roles

Roles, their memberships and privileges on the cluster are not part of pg_dump. Take them separately:

Terminal window
docker exec db pg_dumpall -U postgres --globals-only > globals.sql

3. Check the dump before you trust it

pg_restore --list reads the archive’s table of contents; a truncated or corrupt file fails here:

Terminal window
docker exec -i db pg_restore --list < appdb.dump | head

4. Restore into a scratch container

Start a server with the same major version (or newer) and restore into it:

Terminal window
docker run -d --name pg-restore-test -e POSTGRES_PASSWORD=scratch postgres:17
docker exec -i pg-restore-test psql -U postgres -f - < globals.sql
docker exec pg-restore-test createdb -U postgres appdb
docker exec -i pg-restore-test pg_restore -U postgres -d appdb --exit-on-error < appdb.dump

psql reports that the postgres role already exists — that is expected. Then compare something you know about the data:

Terminal window
docker exec db psql -U postgres -d appdb -Atc "select count(*) from orders"
docker exec pg-restore-test psql -U postgres -d appdb -Atc "select count(*) from orders"
docker rm -f pg-restore-test

Version rules worth remembering

  • pg_dump must be the same major version as the server or newer — running it inside the database’s own container guarantees that.
  • Restore into the same major version or a newer one; restoring into an older major version is not supported.
  • Extensions that need shared_preload_libraries (TimescaleDB, pg_cron) must be configured on the server you restore to.

Where Xtic comes in

The steps above are what Xtic runs for PostgreSQL — the custom-format dump per database, the globals as a separate artefact, the format check — and then it adds what a cron job does not: encryption with your keys, a checksum of what was stored, a signed manifest, retention, an alert when a backup is missing, and scheduled restore drills that repeat step 4 for you. The PostgreSQL page lists exactly what Xtic runs and how it restores.

postgresqldockerrestore