Skip to main content
RunBook Academy

Docker & ContainersXXVIII Β· MaintenanceVolume hygiene

Volume lifecycle β€” orphans, anonymous volumes, and safe reclamation

Intermediate⏱ ~24 mindocker

What you'll learn

  • Explain how anonymous volumes are created and why they accumulate
  • Audit an unattached volume before deleting it
  • Choose between `docker volume prune`, `--all`, and `docker rm -v`
  • Recognise the Compose commands that destroy volume data

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-11

Not yet marked complete on this device.

Run docker volume ls on a host that has been in service for a year and you get something like this:

Read-only / Safevolume ls
$ docker volume ls
DRIVER    VOLUME NAME
local     0e22ff337fce030ca13101cf85a6c2c5a16adaa1bab461f0f89c15214e3c456e
local     1c2f6c9913f64e83719fab7d85f0d8907acd93d03128f282e1eff4b7345951fc
local     1e4617a57468922d11e5997e605efb7e1fe5da073e7d8d78cf6f4dfa2d7d1ae2
local     app_pgdata
local     app_redis
local     6c056b1d1212d3a4c04bedb3974990f8f296db5986a00b048770ea64789cf3bc

Illustrative output

Two of those names mean something. Four are 64-character hex strings that give no hint of what they hold, when they were created, or whether anything would miss them. Volumes are the only Docker object where guessing wrong destroys data that exists nowhere else, so this is the maintenance task that deserves the most care and usually gets the least.

Where the hex names come from

Those are anonymous volumes. Docker creates one, named after a random identifier, whenever a container needs a volume at a mount point and no name was supplied. Three things do this:

Every time that container is replaced without --rm or docker rm -v, the old anonymous volume is left behind and a new one is created. A service that gets redeployed weekly accumulates a volume a week, each holding a stale copy of a data directory.

Auditing before deleting

Never prune a volume you have not looked at. Three commands do the whole audit.

Which volumes are unattached

Read-only / Safedangling
docker volume ls --filter dangling=true

dangling=true means β€œnot referenced by any container”, running or stopped β€” the same population the default prune targets, plus the named ones it will spare.

Read-only / Safevolume sizes
$ docker system df -v
Local Volumes space usage:

VOLUME NAME                                                        LINKS     SIZE
app_pgdata                                                         1         2.11GB
0e22ff337fce030ca13101cf85a6c2c5a16adaa1bab461f0f89c15214e3c456e   0         40.01MB
1c2f6c9913f64e83719fab7d85f0d8907acd93d03128f282e1eff4b7345951fc   0         88B
6c056b1d1212d3a4c04bedb3974990f8f296db5986a00b048770ea64789cf3bc   0         89B

Illustrative output

LINKS is the container reference count. LINKS 0 is an orphan candidate. The size column is the discriminator: an 88-byte volume is a runtime scratch directory nobody will miss. A 40 MB volume held a real dataset.

What is inside

The only reliable answer. Mount it read-only into a throwaway container:

Read-only / Safeinspect contents
VOL=0e22ff337fce030ca13101cf85a6c2c5a16adaa1bab461f0f89c15214e3c456e

docker run --rm -v "$VOL":/audit:ro alpine:3.20 sh -c 'ls -la /audit; du -sh /audit'

A PG_VERSION file and a base/ directory tells you it is a Postgres data directory. An empty tree or a single socket file tells you it is disposable. Two minutes of this beats any amount of reasoning about names.

Choosing the right removal

SituationCommandScope
Container is being removed and its anonymous volume is junkdocker rm -v CONTAINERAnonymous volumes of that container only
Container is short-liveddocker run --rm ...Anonymous volumes removed on exit
Routine cleanup of orphaned anonymous volumesdocker volume pruneUnattached anonymous volumes
Removing a specific volume you have auditeddocker volume rm NAMEExactly that volume
Reclaiming unattached named volumesdocker volume prune -aUnattached volumes of both kinds

The first two are the ones that prevent the problem rather than cleaning it up. docker rm -v removes anonymous volumes associated with the container β€” it never removes a named one, which is what makes it safe to use by default.

Destructiveprevention
# Removes the container and any anonymous volumes it created
docker rm -v old-worker

# For short-lived containers, do it at run time
docker run --rm -v /scratch alpine:3.20 true

Adopt docker rm -v as the habit and the hex strings stop accumulating.

The Compose commands that delete data

Compose has its own vocabulary here and one of the flags is a data-loss button.

CommandContainersNetworksNamed volumesAnonymous volumes
docker compose stopStoppedKeptKeptKept
docker compose downRemovedRemovedKeptKept
docker compose down -vRemovedRemovedDeletedDeleted
docker compose down --rmi allRemovedRemovedKeptKept

docker compose down on its own is safe for data: it removes containers and networks and leaves the volumes: section alone. -v removes the named volumes declared in that section β€” the database, the uploads directory, whatever the stack persists.

A safe reclamation procedure

Destructivevolume reclamation
# 1. List candidates with sizes
docker system df -v | sed -n '/Local Volumes/,$p'

# 2. Record them
docker volume ls --filter dangling=true --format '{{.Name}}' > /tmp/orphans.txt

# 3. Audit every candidate over a size you care about
while read -r vol; do
echo "== $vol"
docker run --rm -v "$vol":/audit:ro alpine:3.20   sh -c 'du -sh /audit; ls -A /audit | head -5'
done < /tmp/orphans.txt

# 4. Remove only what you decided to remove, by name
#    (edit /tmp/orphans.txt first to remove anything you are keeping)
xargs -r -a /tmp/orphans.txt docker volume rm

Step 4 uses docker volume rm with an explicit list rather than docker volume prune, on purpose. The list is a record of a decision. A prune is a decision made by whatever the host looked like at that instant, including any container that happened to be removed thirty seconds earlier.

Knowledge check

Knowledge check Β· 4 questions

  1. Q1. You run `docker run -d --name db postgres:16` with no volume flags. What happens to the database data directory?

  2. Q2. Which column in `docker system df -v` tells you whether a volume is currently referenced by a container?

  3. Q3. Which commands can delete the contents of a NAMED volume? Select all that apply.

  4. Q4. A volume attached to a stopped container can be removed by `docker volume rm` because the container is not running.

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