Skip to main content
RunBook Academy

Docker & ContainersVIII · StorageVolumes

Volumes — named, anonymous, lifecycle

Foundation⏱ ~26 min

What you'll learn

  • Create and manage named volumes
  • Predict what happens to existing image content under a mount point
  • Understand the volume lifecycle and what protects a volume from deletion
  • Back up and migrate volumes, and verify the restore

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.

A volume is a directory on the host, managed by Docker, mounted into a container. Volumes are how stateful workloads (databases, message queues, file uploads) survive container removal and restart.

They are also the only Docker object whose deletion is unrecoverable. An image can be re-pulled, a container recreated from its spec, build cache rebuilt. A volume held the only copy.

Named vs anonymous

Anonymous volumes are created when you docker run -v /data without a name. Docker generates a random 64-character identifier. They are difficult to reference and easy to forget about.

Named volumes are created when you docker volume create pgdata or docker run -v pgdata:/data. The name makes them referenceable in subsequent runs.

docker volume create pgdata
docker run -d --name db -v pgdata:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=REPLACE_ME postgres:16
# Reuse the same volume in another container
docker run --rm -v pgdata:/data alpine:3.20 ls /data

The distinction is not cosmetic. It decides which prune commands can reach the volume, which is covered below.

What happens to the content already there

This is the behaviour most people learn by accident, and it differs between a volume and a bind mount.

Read-only / Safeprove the copy-in
docker volume create nginx-conf-demo

# First use: the volume is empty, so the image content is copied in
docker run --rm -v nginx-conf-demo:/etc/nginx nginx:1.27 ls /etc/nginx

# Same volume, different image path: the content is now the volume's
docker run --rm -v nginx-conf-demo:/audit:ro alpine:3.20 ls /audit
Read-only / Safecopy-in result
$ docker run --rm -v nginx-conf-demo:/audit:ro alpine:3.20 ls /audit
conf.d
fastcgi_params
mime.types
modules
nginx.conf
scgi_params
uwsgi_params

Illustrative output

Where volumes live

Read-only / Safevolume inspect
VOL=pgdata
docker volume inspect "$VOL"
Read-only / Safeinspect output
$ docker volume inspect pgdata
[
  {
      "CreatedAt": "2026-03-04T11:22:19Z",
      "Driver": "local",
      "Labels": {
          "com.docker.compose.project": "shop",
          "com.docker.compose.volume": "pgdata"
      },
      "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
      "Name": "pgdata",
      "Options": {},
      "Scope": "local"
  }
]

Illustrative output

Two fields earn their place here. CreatedAt is the only timestamp Docker gives you for a volume, and it is often the difference between “this is last month’s deployment” and “this is production”. Labels is where Compose records the project and service that owns the volume — the closest thing to provenance you will get, and the reason Compose-managed volumes are so much easier to audit than hand-made ones.

The Mountpoint is a normal directory of normal Linux files. Read it as root for a quick look, but do not build tooling on it: for non-local drivers it is an implementation detail, and on a host using a plugin-backed driver reading it directly shows you an empty directory.

Volume drivers, and what is not one

By default Docker uses the local driver. nfs, cifs, btrfs and zfs are not volume driver names. This trips people up constantly, because the shape of the command looks like it should be a driver.

Network filesystems are reached by passing mount options to the local driver:

docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.0.2.10,rw,nfsvers=4.2 \
  --opt device=:/srv/exports/appdata \
  appdata-nfs

type, o and device are handed to mount(8) more or less verbatim. Real third-party drivers exist and are installed as plugins — rclone is one Docker documents — and they show up in docker info under Plugins: Volume:.

Read-only / Safewhat drivers exist here
docker info --format '{{json .Plugins.Volume}}'

On a stock host that prints ["local"]. Network storage has its own lesson; the point here is that it is the local driver doing the work.

Volume lifecycle

A volume outlives the containers that use it. Removing a container does not remove its volumes, named or anonymous, unless you ask.

What you runNamed volumeAnonymous volume
docker rm CONTAINERKeptKept
docker rm -v CONTAINERKeptRemoved
docker run --rm ...KeptRemoved on exit
docker volume pruneKeptRemoved if unattached
docker volume prune -aRemoved if unattachedRemoved if unattached
docker compose downKeptKept
docker compose down -vRemovedRemoved

The two bold rows in the “named” column are the only ways a named volume disappears without you typing its name. Both are worth knowing by heart.

Backup, and proving the restore

A volume is a directory, so the backup is a tar. The pattern: mount the volume read-only into a throwaway container, mount a host directory for output, use standard tools.

Read-only / Safevolume backup
VOL=pgdata
DEST=/srv/backups

docker run --rm -v "$VOL":/source:ro -v "$DEST":/backup alpine:3.20 tar czf "/backup/$VOL-$(date +%F).tar.gz" -C /source .

Two details that are not decoration. :ro on the source means a mistake in the tar command cannot damage the volume. And for a database, a file-level tar of a running data directory is a crash-consistent copy at best — stop the container first, or use the database’s own dump tool. pg_dump exists for a reason.

Restoring is the same shape in reverse:

Destructivevolume restore
VOL=pgdata-restored
SRC=/srv/backups/pgdata-2026-08-12.tar.gz

docker volume create "$VOL"
docker run --rm -v "$VOL":/target -v "$(dirname "$SRC")":/backup:ro alpine:3.20 tar xzf "/backup/$(basename "$SRC")" -C /target

Now the part people skip. A restore that ran is not a restore that worked:

Read-only / Safeverify the restore
VOL=pgdata-restored

# 1. Is there anything in it, and is it the right order of magnitude?
docker run --rm -v "$VOL":/v:ro alpine:3.20 du -sh /v

# 2. Does it contain the marker files this workload must have?
docker run --rm -v "$VOL":/v:ro alpine:3.20 sh -c 'test -f /v/PG_VERSION && cat /v/PG_VERSION || echo MISSING'

# 3. Does the application actually start against it?
docker run --rm -e POSTGRES_PASSWORD=REPLACE_ME -v "$VOL":/var/lib/postgresql/data postgres:16 postgres --version

Step 2 is the one that fails usefully. A tar restored into the wrong directory level — the classic -C /target versus -C /target/data mistake — produces a volume that is the right size and completely unusable, and only a marker-file check catches it before the database does.

  1. Name every volume. pgdata-prod, not a hex string. A named volume also survives the default docker volume prune.
  2. Label them. docker volume create --label owner=payments --label backup=daily pgdata puts provenance where an auditor can find it.
  3. **Back up with the source mounted :ro.** A read-only source cannot be damaged by a bad tar command.
  4. Restore into a new volume name. Verify it, then swap. Never restore over the live volume as the first attempt.
  5. Verify with a marker file, not a size. A wrong-level tar restores the right number of bytes into an unusable tree.
  6. Separate container cleanup from volume reclamation. Removing a container silently removes a volume protection.

Knowledge check

Knowledge check · 5 questions

  1. Q1. You mount an EMPTY named volume at `/etc/nginx`, a path the nginx image already populates. What does the container see there?

  2. Q2. What does `docker volume rm pgdata` do while a STOPPED container still references pgdata?

  3. Q3. Which of these can delete a NAMED volume and its contents? Select all that apply.

  4. Q4. `nfs` and `cifs` are `--opt type=` values handed to mount(8) by the `local` driver, not volume driver names.

  5. Q5. A volume restore completed, and `du -sh` reports roughly the expected size. What still needs checking?

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