16.3.1.3 Anonymous Volume Confusion
A focused guide to Anonymous Volume Confusion, connecting core concepts with practical Docker and container operations.
Anonymous volume confusion arises from Docker's behavior of automatically creating an unnamed, randomly-identified volume for any VOLUME instruction or volume mount point that is not explicitly given a name, producing a class of practical problems, accumulating orphaned volumes, confusing which volume actually holds a container's real data, and unexpected behavior when combining bind mounts with image-declared volumes, that all trace back to not realizing an anonymous volume was created in the first place.
How anonymous volumes get created without being asked for explicitly
A VOLUME instruction in a Dockerfile causes Docker to automatically create an anonymous volume for that path on every container started from the image, unless something else, an explicit named volume or bind mount, is mounted at that same path instead:
VOLUME /var/lib/postgresql/data
docker run -d postgres
docker volume ls
DRIVER VOLUME NAME
local a1b2c3d4e5f6...
A long, random-looking volume name like this is the signature of an anonymous volume; nothing in the docker run command itself requested a volume, but the image's own VOLUME instruction caused one to be created automatically and silently.
The orphaned volume accumulation problem
Because anonymous volumes are not referenced by any memorable name, and because removing a container does not automatically remove the anonymous volume that was attached to it (unless --rm and a few specific conditions apply), a host running many containers from images with VOLUME instructions can accumulate a large number of orphaned, unnamed volumes over time, each consuming disk space with no obvious indication of what it was for or whether it is still needed:
docker volume ls -f dangling=true
DRIVER VOLUME NAME
local a1b2c3d4e5f6...
local f6e5d4c3b2a1...
The dangling=true filter specifically surfaces volumes not currently attached to any container, which on a host that has been running for a long time without periodic cleanup, frequently reveals a substantial number of these orphaned anonymous volumes accumulated from containers that were started, stopped, and removed without anyone realizing an anonymous volume had been silently created and left behind each time.
Losing track of where actual data lives
The more subtle and consequential version of this confusion occurs when an operator assumes a container's data is ephemeral or assumes it is stored in a specific, named volume, while it is actually persisting in an anonymous volume they were unaware existed, which becomes a real problem the moment that specific container is removed and a new one is started without explicitly attaching the same anonymous volume:
docker run -d --name my-db postgres
docker rm -f my-db
docker run -d --name my-db postgres
The first container's data persisted in an anonymous volume, but the second, replacement container gets an entirely new, different anonymous volume, since nothing connected the two beyond happening to use the same image; the original anonymous volume, and the data inside it, is now orphaned and disconnected from the running service, easy to mistake for genuine data loss when it is more precisely a loss of the connection between a running container and the volume that actually holds its data.
Converting an anonymous volume to a named one deliberately
The fix is straightforward once understood: explicitly naming the volume removes the ambiguity entirely, ensuring the same, identifiable volume is reliably reattached across container replacements:
docker run -d --name my-db -v pgdata:/var/lib/postgresql/data postgres
docker run -d --name my-db -v pgdata:/var/lib/postgresql/data postgres
Both invocations here explicitly reference the named volume pgdata, which means any number of container replacements continue to use the exact same, identifiable, intentional volume, rather than each one silently receiving its own separate anonymous volume.
Bind mounts overriding image-declared volumes
When a bind mount or named volume is explicitly specified at the same path an image's VOLUME instruction declares, that explicit mount takes precedence and no anonymous volume is created for that path at all, which is generally the desired, intended behavior but worth confirming directly when uncertain:
docker run -d -v pgdata:/var/lib/postgresql/data postgres
docker inspect $(docker ps -lq) --format '{{json .Mounts}}'
Checking the actual mounts on a running container directly confirms whether the intended named volume or bind mount is genuinely in effect at the relevant path, rather than assuming based on the run command alone, especially in more complex setups involving Compose files with multiple layered configuration sources.
Anonymous volumes in multi-stage development setups
A common, legitimate use of an anonymous volume is intentionally excluding a specific subdirectory from an otherwise bind-mounted source tree during development, such as preventing a host's node_modules directory from shadowing one installed inside the container:
services:
api:
volumes:
- .:/app
- /app/node_modules
The second entry here, a bare container path with no source specified, creates an anonymous volume specifically for /app/node_modules, which takes precedence over whatever the bind mount above it would otherwise have placed there, intentionally preserving the container's own installed dependencies rather than letting the host's bind-mounted directory override them; this is a legitimate and common pattern, but it is worth recognizing explicitly as the same anonymous volume mechanism responsible for the orphaned volume accumulation problem described earlier, just used deliberately here rather than created accidentally.
Auditing and cleaning up anonymous volumes safely
Before removing dangling anonymous volumes in bulk, confirming none of them are actually the sole copy of needed data is worth doing explicitly, since the random naming makes it impossible to infer their purpose from the name alone:
docker volume ls -f dangling=true -q | xargs -I {} docker run --rm -v {}:/data alpine du -sh /data
Checking the actual size and, where feasible, content of each dangling anonymous volume before removing it avoids accidentally discarding something that was actually still needed but simply disconnected from a currently running container through one of the scenarios described above.
docker volume prune
Once confirmed safe, docker volume prune removes all volumes not currently attached to a running container, anonymous and named alike, which is the appropriate cleanup step after the review above has ruled out anything that should be preserved.
Common mistakes
- Not realizing a
VOLUMEinstruction in an image automatically creates an anonymous volume, leading to silent, unintended accumulation of orphaned volumes over time. - Assuming a removed and replaced container automatically reconnects to the same data it had before, when an anonymous volume from the original container is left behind, disconnected, rather than reattached.
- Mistaking a disconnected anonymous volume scenario for outright data loss, when the actual data may still exist in an orphaned volume that simply was not explicitly reattached.
- Removing dangling volumes in bulk without first checking whether any of them hold data that is not actually backed up elsewhere.
- Not explicitly naming volumes for any service whose data needs to reliably persist and reconnect across container replacements.
Anonymous volume confusion is resolved by always explicitly naming volumes for anything that genuinely needs to persist and reliably reconnect across container replacements, understanding that any VOLUME instruction in an image will otherwise silently create an unnamed, easy-to-lose-track-of volume, and auditing dangling volumes carefully before bulk removal, since their random names give no indication of what they actually contain.