Docker Volumes vs Bind Mounts Explained
The practical differences between Docker volumes and bind mounts, when to pick each, and how to avoid the permission and performance traps.
What you'll learn
- ✓How volumes and bind mounts differ
- ✓Lifecycle and ownership
- ✓Performance characteristics
- ✓Permissions and UID mismatches
- ✓Backup and migration patterns
Prerequisites
- •Familiar with terminals and YAML
What and Why
Containers are designed to be ephemeral; their writable layer disappears with the container. For anything that must survive a restart - a database file, an upload directory, a cache - you need to mount storage from outside the container. Docker gives you two main options: a named volume managed by Docker, or a bind mount straight from the host filesystem.
They look similar in docker run flags but behave differently around lifecycle, portability, performance, and permissions. Picking the wrong one leads to data lost on docker compose down -v or hosts littered with mystery directories.
Mental Model
Bind mount: host:/srv/app/data --> container:/var/lib/app
(you own the path, exact mapping)
Named volume: docker volume "appdata" --> container:/var/lib/app
(Docker owns it under /var/lib/docker/volumes)
Tmpfs: RAM only, vanishes on stop Pick a bind mount when the host path is the source of truth (your source code during development, a config file, a Unix socket). Pick a volume when the container is the source of truth and Docker should manage it (database data, caches, anything you do not want to know the host path of).
Hands-on Example
Local dev with hot reload uses a bind mount of source code:
docker run --rm -it \
-v "$PWD":/app -w /app \
-p 3000:3000 \
node:20 npm run dev
Production-style Postgres uses a named volume:
# compose.yaml
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
ports: ["5432:5432"]
volumes:
pgdata:
Inspect and back up the volume without caring where it lives:
docker volume inspect pgdata
docker run --rm \
-v pgdata:/data \
-v "$PWD":/backup \
alpine tar czf /backup/pg-$(date +%F).tgz -C /data .
A read-only config bind mount is a great pattern:
docker run -d \
-v "$PWD/nginx.conf":/etc/nginx/nginx.conf:ro \
nginx:1.27
The :ro makes the mount read-only so the container cannot corrupt your config.
Common Pitfalls
- Compose
down -vdeletes volumes. That is the entire point of the-v, but it bites people who run it during cleanup. Tag environments and gate the flag. - Bind mount UID mismatch. The container process runs as UID 1000 but the host directory is owned by UID 1001. Files get written but you cannot read them. Either match UIDs (
--user $(id -u):$(id -g)) orchownthe directory. - Bind mount hides image content. Mounting onto
/appreplaces whatever the image baked in. Useful for dev, surprising in prod. - macOS and Windows bind performance. The virtualization layer makes bind mounts slow for large node_modules style trees. Use named volumes for dependencies or enable VirtioFS.
- SELinux denials on RHEL. Append
:z(shared) or:Z(private) to the bind mount to relabel.
Production Tips
- In production, prefer named volumes for stateful data. They are easier to back up, easier to migrate, and survive image upgrades cleanly.
- For shared config and secrets, mount specific files read-only. Avoid bind-mounting whole host directories into prod containers.
- Use a volume driver for remote storage:
local-persist,rexray, or the cloud-native CSI drivers in Kubernetes. Same mental model, different backend. - Back up volumes by running a short-lived helper container that mounts the volume and writes a tarball to object storage. Schedule it from cron or a CI runner.
- Document the data directory contract for each container: which path is durable, what owns the path, how to restore it. Future-you will be grateful.
Wrap-up
Volumes and bind mounts cover the same gap - persistence beyond the container - but optimize for different ends. Bind mounts give you exact host control, perfect for dev and config files. Named volumes give you portable, Docker-managed storage, perfect for state in production. Pick deliberately, mount read-only where you can, and put a backup story in place before you need it.
Related articles
- Docker Docker Healthchecks and Restart Policies Explained
Healthchecks tell Docker if a container is alive. Restart policies tell it what to do when it is not. Together they keep your services running.
- Docker Docker Image Layer Caching Strategies for Faster Builds
Learn how Docker's layer cache really works and the ordering tricks that turn a 5-minute build into a 20-second one without sacrificing correctness.
- Docker Docker Networking: Bridge, Host, and Overlay Networks Explained
A clear guide to Docker's three most common network drivers, when to pick each one, and how packets actually flow between containers in real deployments.
- Docker Docker Overlay Filesystem Explained: Layers, lowerdir, upperdir
How Docker's overlay2 storage driver stacks read-only image layers under a writable container layer. Learn the directory anatomy and the copy-on-write behavior.