Skip to content
shellhacks.dev
Databases

How to Dump and Restore a Docker Database Volume Fast

Dump and restore a database in Docker: pg_dump and mysqldump via docker exec, volume backups, consistency, restoring into a new container and speed.

6 min read
Summarize in AI
Illustration for "How to Dump and Restore a Docker Database Volume Fast": a terminal session, progress bars, a disk usage gauge.

A database in a container keeps its files in a volume, and the instinct is to copy the volume. A live copy of a running database's files is generally inconsistent and unusable. The reliable route is the database's own dump tool, run inside the container, which gives a consistent snapshot you can move to any host. This is the Docker-specific version of pg_dump backups and volume backups. The examples assume Docker 27, PostgreSQL 17 and MySQL 8.4.

Dump with docker exec

Shell
docker exec -T db pg_dump -U app -Fc appdb > appdb.dump
docker exec -T db pg_dump -U app appdb | gzip > appdb.sql.gz
docker exec -T mysql sh -c 'mysqldump --single-transaction --routines --triggers -uroot -p"$MYSQL_ROOT_PASSWORD" appdb' | gzip > appdb.sql.gz
ls -lh appdb.*
Text
-rw-r--r-- 1 alice alice 41M Nov  3 11:02 appdb.dump
-rw-r--r-- 1 alice alice 38M Nov  3 11:02 appdb.sql.gz

-T disables the pseudo-terminal so binary or large output is not altered by line-ending conversion. -Fc is PostgreSQL's custom format, compressed and restorable selectively and in parallel. MySQL's --single-transaction takes a consistent snapshot of InnoDB tables without locking them. The root password is read from the container's own environment inside sh -c, so it does not appear on the host's command line. The sizes shown are example values.

Restore into a new container

Shell
docker run -d --name db2 -e POSTGRES_PASSWORD=change-me -e POSTGRES_USER=app -e POSTGRES_DB=appdb -v db2data:/var/lib/postgresql/data postgres:17
until docker exec db2 pg_isready -U app -q; do sleep 1; done
docker exec -i db2 pg_restore -U app -d appdb --no-owner --jobs=4 < appdb.dump
gunzip -c appdb.sql.gz | docker exec -i db2 psql -U app -d appdb -v ON_ERROR_STOP=1

Use -i (not -T) when the container reads standard input. Create the empty database first, then load. --jobs restores tables in parallel and needs the custom or directory format, not plain SQL. ON_ERROR_STOP makes psql stop at the first error rather than continue with half a database. A restore to the same major version is the safest; dumps load into newer versions, not older ones.

Volume backups: only when stopped

Shell
docker stop db
docker run --rm -v dbdata:/data:ro -v "$PWD":/backup alpine tar -czf /backup/dbdata.tgz -C /data .
docker start db
docker run --rm -v dbdata_restore:/data -v "$PWD":/backup alpine tar -xzf /backup/dbdata.tgz -C /data

Archiving the data directory is a physical backup and works only while the database is stopped, or with a storage snapshot taken at one instant. It restores exactly to the same version and architecture. Use it for a quick full copy before an upgrade, and use logical dumps for routine backups. The difference is explained in logical versus physical backups. For PostgreSQL, pg_basebackup takes a physical backup from a running server safely.

Speed

Shell
docker exec -T db pg_dump -U app -Fd -j 4 -f /tmp/dumpdir appdb && docker cp db:/tmp/dumpdir ./dumpdir
docker exec -T db pg_dump -U app appdb | zstd -T0 -3 > appdb.sql.zst

The directory format with -j dumps tables in parallel, and zstd compresses faster than gzip at a similar ratio. A dump taken inside the container competes for CPU with the database, so schedule it off-peak, as in scheduling database maintenance. Copy the dump off the host, encrypt it if it holds sensitive data and keep several generations.

Prove the restore works

Shell
docker run -d --rm --name verify -e POSTGRES_PASSWORD=x postgres:17
until docker exec verify pg_isready -q; do sleep 1; done
docker exec -i verify pg_restore -U postgres -d postgres --create --no-owner < appdb.dump
docker exec verify psql -U postgres -d appdb -Atc "SELECT count(*) FROM orders"
docker stop verify

Restore into a disposable container and run a query. This takes a minute and finds problems early. Automate it, as described in verifying backups automatically.

Choosing logical or physical backups

A logical dump, produced by pg_dump or mysqldump, is a set of SQL statements or a custom archive that rebuilds the data. It is consistent without stopping the database, portable between versions and platforms, and lets you restore a single table. It is slow to restore on large databases, since the indexes must be rebuilt. A physical backup copies the data files, either with the database stopped or with a tool that handles consistency, such as pg_basebackup. It restores quickly and supports point-in-time recovery with archived logs, but it ties you to the same major version and architecture. For small to medium databases in containers, logical dumps are the simplest, and for large ones a physical approach becomes necessary. Many setups use both.

The decision depends on the time you can afford to restore and the size of the database. Measure a restore of the real dump before deciding it is fast enough.

Running the dump from outside the container

The usual pattern runs the dump tool inside the database container with docker exec and writes the output to the host, so no client tools are needed there. Redirect to a file on the host and compress on the fly: the data crosses the pipe once and is compressed before it touches the disk. Pass no passwords on the command line; use environment variables set in the container, a password file or socket authentication. Use the custom format for PostgreSQL, -Fc, which compresses and allows selective restore, and a single transaction for MySQL with --single-transaction so InnoDB tables are consistent. Avoid the terminal flag in scripts, -t, since it adds carriage returns to the output and corrupts a binary dump; use -i only when feeding input.

Check the exit status of the whole pipeline with pipefail. A failed dump piped through gzip produces a valid but empty-looking archive, and nothing complains.

Restoring safely

Restore to a fresh container first, never over a live database as a test. Start an empty instance of the same major version, create the database, and load the dump with pg_restore or by piping SQL into the client. Compare table row counts with the source, run the application's smoke tests against it and look at the logs for errors, since a restore can finish with warnings about missing roles or extensions that matter later. Watch for differences between the character set and collation of the source and the target. A restore under a different locale can silently change sort order and break unique indexes. For the real recovery, plan the order: stop writers, restore, verify, then reconnect.

Remember roles and global objects. A dump of one database does not include users, so dump them separately with pg_dumpall --globals-only, or the restored data has no owner.

Automating and trusting the backup

A scheduled job should dump, compress, checksum, copy offsite and prune old copies, then alert on any failure and on the absence of success. A restore test belongs in the schedule: restore the latest dump into a throwaway container weekly, run a few queries and record the result. That is the only proof the backup works. Encrypt dumps that leave the host, store the key separately and document the restore procedure with the exact commands, so someone under pressure can follow it. Include the Compose file and the environment configuration in the backup, because data without the definition of the service takes time to reassemble. Keep retention matched to the need, and make sure the backup storage is not reachable with the same credentials as the database server, so one compromise cannot delete both.

Where this does not apply

Very large databases (hundreds of gigabytes) need physical backups with WAL archiving for point-in-time recovery, see point in time recovery. A dump does not capture roles and global objects in PostgreSQL; use pg_dumpall --globals-only for those. See the pg_dump documentation and the official Postgres image notes.

Also inDocker
Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.