Volumes & Bind Mounts
Summary
- Volumes and bind mounts are the two ways to give a container storage that outlives its ephemeral writable layer.
- Insight: Named volumes are engine-managed and portable; bind mounts couple you to a specific host path and its exact permissions.
- Key Concepts: named volume, bind mount, tmpfs mount,
--mountvs-v, UID/GID mapping, read-only mounts. - When to Use: Volumes for databases and app state; bind mounts for local development source code; tmpfs for secrets and scratch.
- Limitations: Both are single-host in plain Docker; multi-node clusters need CSI-backed PersistentVolumes instead.
- Related: How containers get storage, storage permissions pitfalls, best practices.
Recipe
Create a named volume and mount it into a container.
docker volume create pgdata
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16The database writes to /var/lib/postgresql/data, but the bytes live in pgdata and survive docker rm db.
For live-editing source during development, bind mount a host directory instead.
docker run --rm -v "$PWD/src:/app/src" node:22 node /app/src/index.jsWorking Example
A Compose stack usually mixes a named volume for data with a read-only bind mount for config.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
cache:
image: redis:7
tmpfs:
- /data
volumes:
pgdata:The pgdata named volume holds durable database files.
The init.sql bind mount is read-only (:ro) so the container cannot alter your host file.
The Redis tmpfs keeps its scratch data in RAM, which never touches disk.
docker compose up -d
docker volume inspect myproject_pgdataDeep Dive
Named volumes vs bind mounts
A named volume is created and stored by the engine under its own data root, and Docker tracks its lifecycle.
Because the engine owns it, a fresh empty volume is initialized with the contents of the container path on first mount, and it is portable across hosts via backup or volume plugins.
A bind mount points at an exact host path you choose, and it shadows whatever was at that path in the image.
There is no lifecycle tracking - delete the host directory and the data is gone.
The --mount syntax
The older -v shorthand is terse but ambiguous; --mount is explicit and preferred for anything non-trivial.
docker run -d \
--mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
--mount type=bind,source="$PWD/conf",target=/etc/app,readonly \
postgres:16A key difference: with -v, a bind source that does not exist is created as an empty directory; with --mount, a missing bind source errors out, which catches typos.
UID/GID and how ownership travels
Files in a volume or bind mount keep their numeric UID and GID, not usernames.
If your image runs as UID 1000 but the host directory is owned by UID 0, writes fail with permission errors.
docker run --rm -v "$PWD/data:/data" --user 1000:1000 alpine:3.20 \
sh -c 'touch /data/ok || echo "permission denied"'The fix is to align the container user with the directory owner, which the next page covers in depth.
tmpfs mounts
A tmpfs mount lives in host memory and is never written to disk.
It is ideal for sensitive scratch data - decrypted secrets, session files - because it vanishes when the container stops and leaves no on-disk trace.
Gotchas
Mounting over a non-empty image directory hides the image's files for the life of the mount; a bind mount of an empty host dir onto /app makes the app "disappear".
Named volumes initialize from the image path only when empty - if the volume already has data, the image's default contents are ignored.
Anonymous volumes (-v /data with no name) accumulate as untagged clutter; prune them with docker volume prune and prefer named volumes.
Bind mounts on Docker Desktop cross a virtualization boundary, so heavy I/O (node_modules, database files) is far slower than a named volume - keep those in volumes.
Removing a container does not remove its named volumes; use docker rm -v or docker compose down -v when you truly want the data gone.
Alternatives
Named volume: the default for persistent app and database state; portable and engine-managed.
Bind mount: best for development source code and injecting config files from the host.
tmpfs mount: for secrets and scratch that must never hit disk.
Volume plugins / CSI: for networked or cloud storage; in Kubernetes this becomes a PersistentVolumeClaim bound by a CSI driver, the correct multi-node choice.
FAQs
Will my data survive docker rm?
Yes, if it is in a named volume or bind mount. Data in the container's writable layer is deleted.
Should I use -v or --mount?
Prefer --mount for clarity and because it errors on missing bind sources instead of silently creating them.
Why did my app files disappear after mounting? A mount shadows the image contents at that path. Mount to a different path or seed the volume first.
How do I make a mount read-only?
Append :ro with -v, or add readonly with --mount.
Do volumes work across a Kubernetes cluster? Plain Docker volumes are single-host. Use a CSI-backed PersistentVolumeClaim in Kubernetes.
Related
- How Containers Get Networking and Storage
- Storage Permissions Pitfalls
- User-Defined Networks
- Networking & Storage Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).