Docker Volumes Concept
Bind mounts got the job done, but they made you responsible for everything — the path, the permissions, the portability. Docker volumes take that burden off your hands entirely, which is exactly why they’re the recommended, go-to storage option for almost every real production use case. But they come with their own set of quirks that catch people off guard, so let’s cover both the concept and the gotchas together.
Docker Volumes Concept
A volume is storage that Docker creates and manages for you, living outside the container’s writable layer, inside Docker’s own managed area on the host — typically /var/lib/docker/volumes/. Unlike a bind mount, you don’t specify a host path yourself; Docker handles where the data physically sits.
flowchart LR
subgraph Host["Host Machine"]
DA["Docker-Managed Area<br/>/var/lib/docker/volumes/my-data/_data"]
end
subgraph C1["Container A"]
M1["/var/lib/mysql"]
end
subgraph C2["Container B"]
M2["/backup-reader"]
end
DA <==> M1
DA <==> M2
Create one explicitly:
docker volume create my-dataThen attach it to a container:
docker container run -d \
--name my-db \
--mount type=volume,source=my-data,target=/var/lib/mysql \
mysql:8Or with the shorter -v flag:
docker container run -d --name my-db -v my-data:/var/lib/mysql mysql:8Why Volumes Are the Default Choice
- Docker manages the lifecycle. Creation, location, and cleanup are all handled through Docker’s own commands — no manual host paths to track.
- Portable across environments. A named volume works the same way whether you’re on your laptop, a CI runner, or a production server, unlike a bind mount tied to a specific host directory.
- Safer permission handling. Docker manages volume permissions consistently, sidestepping most of the host-vs-container UID mismatches bind mounts run into.
- Pluggable via volume drivers. Need your data backed by NFS, an AWS EBS volume, or another cloud block-storage system instead of local disk? Volume drivers let you swap the backing storage without changing how your app talks to
/var/lib/mysql. This is huge for production environments needing durability, backups, or multi-host access. - Works well with orchestration. Tools like Docker Swarm and Kubernetes lean heavily on named, driver-backed volumes to move persistent data around a cluster reliably.
Tip
If you don’t explicitly need a specific host path (which is rare), default to a volume. It’s the boring, safe choice — and boring is good for production data.
The Gotchas Nobody Warns You About
This is the part that trips up almost everyone at some point, so let’s go through it carefully.
1. docker container rm doesn’t remove volumes by default.
Removing a container leaves its attached named volumes fully intact — which is usually what you want. But it also means volumes silently pile up over time if you’re not cleaning up deliberately:
# This removes the container, but the volume "my-data" survives
docker container rm my-db
# This removes the container AND its anonymous volumes
docker container rm -v my-dbNote that the -v flag here only removes anonymous volumes attached to that container — named volumes survive even with -v, since Docker assumes a named volume was created intentionally and might be reused elsewhere.
2. Anonymous volumes are a common source of clutter. If you mount a container path without naming a volume, Docker creates one with a random hash as its name:
docker container run -d -v /var/lib/mysql mysql:8You now have a volume like a3f9c8e1d2b4... with no obvious link back to what created it. Multiply this across months of docker container run commands and you’ll end up with dozens of orphaned, unidentifiable volumes eating disk space. Always prefer named volumes unless you have a specific throwaway use case.
3. A typo in a volume name silently creates a new, empty volume.
This one’s sneaky. If you meant to reuse my-data but typo’d my-dta, Docker won’t error — it’ll happily create a brand-new, empty volume called my-dta and mount that instead. Your app then starts up against what looks like a “fresh install,” and it’s easy to lose a few confused minutes wondering where your data went.
4. Volumes only auto-populate from the image on their first use.
If the container’s target path already contains files from the image (say, a default config baked into /etc/myapp), and you mount an empty volume over it, Docker copies the image’s existing files into the volume — but only the very first time that volume is used. On every subsequent mount, Docker leaves the volume’s contents as-is, even if the image changes. This is exactly the desired behavior for persisting data, but it surprises people who expect volume contents to always mirror the current image.
5. Concurrent writes from multiple containers aren’t automatically safe. Docker will happily let two containers mount and write to the same volume simultaneously — it won’t stop you. But whether that’s actually safe depends entirely on what’s writing. A database engine like PostgreSQL expects to own its data directory exclusively; running two instances against the same volume risks corruption. Lightweight, single-writer databases like SQLite are especially fragile here. If multiple containers genuinely need concurrent access to the same data, make sure the application layer (or the volume driver, for network filesystems) is actually designed for concurrent access — Docker itself won’t protect you.
List and Inspect Volumes
A few commands worth keeping handy while working with the gotchas above:
# List all volumes
docker volume ls
# Inspect a volume (see its actual host path, driver, labels)
docker volume inspect my-data
# Find dangling (unused) volumes before cleaning them up
docker volume ls -f dangling=trueWe’ll build on these — along with backup, restore, and cleanup strategies — in the next section on managing a volume’s full lifecycle.