Volumes vs Bind Mounts
Comparison at a glance
Section titled “Comparison at a glance”| Property | Named Volume | Bind Mount |
|---|---|---|
| Path managed by | Docker | You |
| Host path required | No | Yes |
| Portability | High — works on any host | Low — path must exist on every host |
| Performance | Optimised by Docker storage driver | Direct host filesystem (fast on Linux, slower on macOS/Windows) |
| Backup | docker volume inspect + tar container | Copy files directly from the host path |
| Typical use case | Database files, app state, uploads | Source code, config files in development |
| Works in production | Yes | Rarely (depends on host layout) |
When to use each
Section titled “When to use each”Use named volumes when:
Section titled “Use named volumes when:”- You need data to survive container replacements (databases, object storage, cache).
- You are deploying to a remote host or CI/CD environment where the host path may differ.
- You want Docker to own the lifecycle of the storage.
Use bind mounts when:
Section titled “Use bind mounts when:”- You are developing locally and want live code reload without rebuilding the image.
- You need to inject a config file from the host into the container at runtime.
- You are running tools (linters, compilers) that produce output you want directly on the host.
Read-only mounts
Section titled “Read-only mounts”Both volume types support read-only mounts. The container can read data but cannot write to the mount target.
Named volume, read-only:
docker run --rm -v mydata:/app/data:ro alpine ls /app/dataBind mount, read-only:
docker run --rm -v $(pwd)/config:/etc/app/config:ro myimageWith --mount syntax (both types):
# Volumedocker run --rm --mount type=volume,source=mydata,target=/app/data,readonly myimage
# Binddocker run --rm --mount type=bind,source=$(pwd)/config,target=/etc/app/config,readonly myimageRead-only mounts are a security best practice when the container only needs to read the data — they prevent a compromised or buggy process from overwriting important files.
Hands-on practice
Section titled “Hands-on practice”The snippet below demonstrates both mount types side by side so you can observe the difference in a single session.
# --- Named volume ---
docker volume create compare-vol
# Write via container
docker run --rm -v compare-vol:/data alpine sh -c "echo 'from named volume' > /data/note.txt"
# Read from a new container (data persists)
docker run --rm -v compare-vol:/data alpine cat /data/note.txt
# --- Bind mount ---
mkdir -p /tmp/compare-bind
echo 'from bind mount' > /tmp/compare-bind/note.txt
# Read from container via bind mount
docker run --rm -v /tmp/compare-bind:/data alpine cat /data/note.txt
# --- Read-only mount ---
docker run --rm -v /tmp/compare-bind:/data:ro alpine sh -c "cat /data/note.txt && echo 'trying to write...' && echo 'blocked' > /data/note.txt || echo 'Write blocked as expected'"
# Clean up
docker volume rm compare-vol
rm -rf /tmp/compare-bindTrade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Named volume | Docker manages it, portable across hosts, easy to back up with a throwaway container | Cannot browse files directly on the host filesystem — must mount into a container first |
| Bind mount | Direct access to a path on the host, great for a dev workflow that edits files often | Tied to that host’s directory layout, hard to move between environments |
Common mistakes
Section titled “Common mistakes”- Picking a bind mount for production database data because “seeing the files directly is easy,” when a named volume is the more correct choice.
- Running
docker compose down -vwhen you only meant to stop containers, deleting the named volume tied to a service along with them. - Never setting up a backup plan for a named volume, assuming Docker backs it up automatically.
💡 Real-world example
The official Docker Hub image documentation for Postgres recommends mounting a named volume at
/var/lib/postgresqlfor Postgres 18 and later (/var/lib/postgresql/datafor 17 and earlier) as the standard production pattern, while a bind mount is only used during development for convenience when editing config or source code (hub.docker.com/_/postgres).