Skip to main content
RunBook Academy

Docker & ContainersXXI Β· BackupWhy not docker commit

Why `docker commit` is not a backup

Foundation⏱ ~20 mindocker

What you'll learn

  • State precisely which bytes `docker commit` captures and which it silently omits
  • Prove the omission on a running container rather than taking it on trust
  • Explain why a `docker commit` restore appears to succeed while losing all persistent data
  • Name the three artefacts that actually constitute a restorable Docker backup

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-12

Not yet marked complete on this device.

docker commit web01 myorg/web01:snapshot creates an image from a running container. It finishes in a second, it produces something you can docker run, and it feels like a snapshot of the machine. It is a reasonable-looking answer to β€œhow do I back this up before I touch it”.

The Docker CLI reference disposes of it in one sentence:

Commits do not include any data contained in mounted volumes.

Read that against your own stack. The Postgres data directory is a volume. The uploads directory is a volume. The Let’s Encrypt account key is a volume. The docker commit image contains none of them, reports no warning, and produces no error. It contains the parts of the container you did not care about and omits the parts you did.

What the command actually captures

Read-only / Safecommit
CONTAINER=web01

docker commit --author 'ops@example.com' --message 'pre-upgrade snapshot' "$CONTAINER" myorg/web01:snapshot

The full option set is four flags: -a/--author, -m/--message, -c/--change (apply a Dockerfile instruction to the result) and --no-pause. There is no --include-volumes. There has never been one.

Proving it, in ninety seconds

Do not take the sentence on trust. This runs on any host with a spare gigabyte and demonstrates the failure end to end.

Service impact possibledemonstrate the gap
# A container with one volume and one file outside it
docker volume create commit-demo
docker run -d --name commit-demo -v commit-demo:/data alpine:3.20 sleep 3600

# One file on the volume, one on the writable layer
docker exec commit-demo sh -c 'echo "THE IRREPLACEABLE DATA" > /data/important.txt'
docker exec commit-demo sh -c 'echo "junk" > /tmp/scratch.txt'

# Commit
docker commit commit-demo commit-demo:snapshot

# Now run the "backup" on a clean volume and look for both files
docker run --rm -v "$(docker volume create)":/data commit-demo:snapshot sh -c 'echo "--- /tmp:"; ls -la /tmp/scratch.txt; echo "--- /data:"; ls -A /data'
Read-only / Safethe result
$ docker run --rm ... commit-demo:snapshot
--- /tmp:
-rw-r--r--    1 root     root             5 Aug 12 09:14 /tmp/scratch.txt
--- /data:

Illustrative output

/tmp/scratch.txt β€” a file nobody would miss β€” is in the image. /data is empty. The command exited 0. Nothing anywhere in that sequence said the word β€œvolume”.

Destructiveclean up the demo
docker rm -f commit-demo
docker volume rm commit-demo
docker image rm commit-demo:snapshot

The verification that would have caught it

Before trusting any container-level snapshot, ask the container what it has mounted. This is the single most useful command in the lesson.

Read-only / Safemount inventory
CONTAINER=web01

docker inspect --format '{{range .Mounts}}{{.Type}} | {{.Name}}{{.Source}} -> {{.Destination}}
{{end}}' "$CONTAINER"
Read-only / Safeinspect .Mounts
$ docker inspect --format '{{range .Mounts}}...' web01
volume | app_pgdata -> /var/lib/postgresql/data
volume | app_uploads -> /srv/uploads
bind | /etc/myapp/config.yaml -> /etc/myapp/config.yaml
volume | 4f1a9c8e2b7d0a63f5e8c1b4d7a0f3e6c9b2d5a8f1e4c7b0a3d6e9c2b5f8a1d4 -> /var/lib/redis

Illustrative output

Every line is a hole in a commit-based backup. The last one β€” an anonymous volume with a hex name, created because the image declared a VOLUME and nobody supplied a mount β€” is the one people are surprised by, because they did not know it was there.

The other four problems

Even where volumes are not involved β€” a genuinely stateless container β€” commit is a poor backup artefact.

Propertydocker commit imageA real backup
ReproducibleNo Dockerfile, no build context, no provenanceDockerfile in git, pinned base digest
AuditableLayer diff of unknown originReviewable source
Restorable in placeNo. There is no docker uncommitData restored under the same running service
Independent of the registryNo. If the registry is the outage, so is the backupCopies on separate media
Sized sensiblyIncludes logs, apt caches, core dumps, /tmpOnly the data

The β€œnot restorable in place” point is the one that gets underrated. Backups are usually needed for partial loss β€” one table dropped, one directory deleted β€” where the requirement is to put some bytes back while everything else keeps serving. A commit image can only be run as a new container. There is no operation that projects it back onto a live system.

What an actual Docker backup consists of

Three artefacts, and you need all three. Any one of them missing turns recovery into archaeology.

Read-only / Safethe three artefacts
STACK=/srv/app
STAMP=$(date +%F)
DEST=/backup

# 1. DATA - via the application's own tool where one exists
docker exec -t app-db pg_dump -U app --format=custom app > "$DEST/app-$STAMP.dump"

# 1b. DATA - for volumes with no dump tool, the volume contents
docker run --rm -v app_uploads:/source:ro -v "$DEST":/backup alpine:3.20 tar czf "/backup/uploads-$STAMP.tar.gz" -C /source .

# 2. DEFINITION - the fully resolved Compose model, image tags pinned to digests
docker compose -f "$STACK/compose.yaml" config --resolve-image-digests > "$DEST/compose-$STAMP.yaml"

# 3. SECRETS - from wherever they authoritatively live, never from the host
#    (export from your secret manager here; do not scrape container env)

Artefact 2 deserves the emphasis. docker compose config renders the merged, variable-interpolated model that the engine would actually apply β€” the file after .env substitution, extends, override files and short-form-to-long-form normalisation. --resolve-image-digests rewrites every image: tag to an immutable name@sha256:... reference. Restoring against nginx:1.27 a year later gives you a different nginx:1.27 than the one that was running; restoring against a digest gives you the same bytes.

When docker commit is the right tool

It has a real job, and it is forensic rather than protective. In each of these the artefact is evidence, not a recovery point:

  • A container is misbehaving and you must restart it now. Commit first, so you can go back and look at the filesystem after service is restored. The alternative is destroying the evidence.
  • Capturing a compromised container for analysis before removing it, so the writable layer β€” dropped binaries, modified /etc/passwd, whiteouts over system files β€” can be examined offline.
  • Reproducing a build that only fails on one machine. Commit the failing build container and diff it against a working one.

For that last case there is a sharper tool: docker diff CONTAINER lists every path added (A), changed (C) or deleted (D) in the writable layer, which is usually the actual question and does not require producing an image at all.

Knowledge check

Knowledge check Β· 5 questions

  1. Q1. A container runs with `-v pgdata:/var/lib/postgresql/data`. You run `docker commit` on it. What is in the resulting image?

  2. Q2. You restore by running a committed database image. The container starts, the health check passes, and the application connects. What has most likely happened?

  3. Q3. Which artefacts does a `docker commit` image fail to preserve? Select all that apply.

  4. Q4. Which command tells you, before you rely on a snapshot, exactly what data a commit would omit?

  5. Q5. By default `docker commit` pauses the container and its processes while the image is created, and `--no-pause` disables that behaviour.

Passing score: 75%. Answers are checked in this browser.