Docker Volumes vs Bind Mounts: What's the Difference
Docker volumes are managed by Docker and portable; bind mounts map a host path directly into a container. When to use each for persistent data.
Volumes and bind mounts are the two main ways Docker persists data outside a container’s writable layer. A volume is a storage location Docker creates and manages itself, independent of the host’s directory structure; a bind mount maps a specific path on the host filesystem directly into the container. Both solve the same underlying problem — a container’s filesystem disappears when the container is removed — but they solve it with very different tradeoffs around portability, performance, and control.
Why containers need either one
By default, everything written inside a running container lives in a thin writable layer on top of its image, and that layer is deleted the moment the container is removed. That’s fine for stateless processes, but it’s a problem for anything that needs to survive restarts: a database’s data directory, uploaded files, or configuration you want to edit without rebuilding the image. Volumes and bind mounts both attach external storage to a container so writes land somewhere that outlives the container itself.
What a volume is
A volume is created and managed entirely by Docker, typically stored under /var/lib/docker/volumes/ on Linux hosts, though the exact location is an implementation detail you’re not meant to touch directly. You create and reference one by name:
docker volume create db-data
docker run -d -v db-data:/var/lib/postgresql/data postgres
Docker owns the lifecycle: docker volume ls, docker volume inspect, and docker volume rm all operate on volumes without you needing to know or care about the underlying host path. Volumes can also be backed by storage drivers for network or cloud storage, which is part of why they’re the more portable option — the same docker run command works whether the volume driver points at local disk or a remote NFS share.
What a bind mount is
A bind mount instead maps an existing path on the host directly into the container, byte for byte:
docker run -d -v /home/user/app/src:/app/src node:20
Whatever’s at /home/user/app/src on the host appears at /app/src inside the container, and changes in either direction are immediately visible in the other. There’s no Docker-managed abstraction in between — you’re pointing directly at a real filesystem path, which means the container can see (and modify) anything that path grants access to, including files well outside what you intended if the path is too broad.
Volumes vs bind mounts
| Volumes | Bind mounts | |
|---|---|---|
| Managed by | Docker | You (raw host path) |
| Location | Docker-chosen, abstracted | Exact host path you specify |
| Portability | Portable across hosts/drivers | Tied to that host’s directory layout |
| Performance (Linux) | Native filesystem performance | Native filesystem performance |
| Performance (Docker Desktop on macOS/Windows) | Better, avoids the VM filesystem bridge | Can be slower due to host/VM file sharing |
| Typical use | Production persistent data (databases, queues) | Local development (live code mounts) |
| Initialized from image | Yes — volume is pre-populated from image content on first use | No — host content overwrites what’s in the image |
When to use volumes
Volumes are the right default for anything that needs durable, production-grade storage: a database’s data directory, a message queue’s persisted state, or any data you want Docker (and orchestration tools built on it) to manage without depending on a specific host’s directory structure. They’re also the better choice when running on Docker Desktop for macOS or Windows, where a bind mount into the Linux VM that backs the Docker engine can be noticeably slower than a volume for filesystem-heavy workloads. When you scale beyond a single host into Kubernetes, the closest equivalent to a Docker volume is a PersistentVolume, which follows the same idea of storage managed independently of any one node.
When to use bind mounts
Bind mounts shine in local development, where the whole point is that edits on the host are reflected instantly inside the running container — mount your source directory and a file-watcher-driven dev server picks up changes without rebuilding the image. They’re also useful for injecting configuration files, certificates, or secrets from a known host path, or for sharing something like the Docker socket itself between the host and a container. Docker Compose commonly uses bind mounts in development configurations for exactly this reason, often paired with a separate production configuration that swaps them for named volumes.
A third option: tmpfs mounts
For data that shouldn’t be written to disk at all — secrets held only in memory, or high-churn temporary files — Docker also supports tmpfs mounts, which live purely in the host’s memory and vanish when the container stops. They’re not a substitute for either volumes or bind mounts, but worth knowing about when persistence is explicitly not what you want.
Common pitfalls
- Orphaned volumes. Removing a container with
docker rmdoesn’t delete its volumes by default, which is usually what you want — but it also means volumes can silently accumulate.docker volume pruneremoves ones no container references. - Permission mismatches. A bind-mounted directory keeps the host’s file ownership and permissions, which can conflict with the UID a process runs as inside the container, a frequent source of “permission denied” errors that don’t show up with Docker-managed volumes.
- Accidentally shadowing image content. Bind-mounting over a directory that already has files baked into the image (like
node_modulesin some setups) replaces it entirely with the host’s version, which may be empty or stale.
The takeaway
Both volumes and bind mounts attach persistent storage to a container, but they differ in who manages it: Docker manages volumes and abstracts away the host path, while bind mounts point directly at a host directory you control. Reach for volumes when you want portable, Docker-managed persistence — especially for production data like databases — and reach for bind mounts when you want a live, two-way link between a specific host path and the container, which is exactly what local development workflows need.
Tagged
Keep reading
Chisato · · 5 min read Docker Networking Modes: Bridge, Host, and Overlay
Docker's default bridge network isolates containers with NAT; host mode shares the host's network stack; overlay connects containers across hosts.
Chisato · · 4 min read Docker vs Podman: Container Runtimes Compared
Docker runs containers through a persistent root daemon; Podman runs them daemonless and rootless by default. What that architectural split changes.
The Lycoris Team · · 4 min read Docker Multi-Stage Builds Explained
Multi-stage builds let a Dockerfile use one stage to compile code and a separate, minimal stage to ship it — cutting image size and attack surface.