Volumes, bind mounts and tmpfs
A container’s filesystem dies with the container. Three mechanisms outlive it, and picking the wrong one is a portability or a data-loss problem.
| Type | Lives | Use for |
|---|---|---|
| Named volume | Docker-managed area | persistent data |
| Bind mount | a host path | local development |
| tmpfs | RAM only | secrets, scratch |
docker run -v pgdata:/var/lib/postgresql/data postgres
docker run -v "$PWD/src:/app/src" myapp
docker run --tmpfs /tmp myappNamed volumes are the production default
Docker creates and owns the storage, so nothing in the command depends on the host’s directory layout. That is what makes the same Compose file work on a laptop, in CI and on a server.
They also work with volume drivers — the same declaration can be backed by NFS, EBS or a cloud file share, which is how a volume outlives a host and not just a container.
services:
db:
image: postgres:18
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:They survive, deliberately
Removing a container leaves its volumes behind. That is the whole point, and also why unused volumes accumulate:
docker compose down # volumes survive
docker compose down -v # volumes deleted
docker volume prune # every unused volumeGotcha:
docker volume prunedeletes anything not currently attached to a container, which includes the database volume of a stack you have merely stopped. It is the one Docker command worth reading twice before running.
Bind mounts: development, and the sharp edges
A bind mount maps a host path straight into the container, so an edit on the host is visible immediately. That live-reload loop is what they are for.
Three things go wrong with them in production:
- Host coupling. The path must exist, with the right contents, on every machine that runs it.
- Permissions. The container’s UID must be able to read and write the host directory. On Linux a root-owned bind mount and a non-root container is the most common “permission denied” in Docker.
- Performance. On macOS and Windows the mount crosses a VM boundary; a
large
node_modulesover a bind mount is dramatically slower than native.
The anonymous-volume trick
volumes:
- ./:/app # source from the host
- /app/node_modules # keep the image's copyThe second line masks the first for that subdirectory, so container-installed dependencies are not shadowed by the host’s. It looks like a typo and is deliberate — worth recognising.
tmpfs: never touches disk
docker run --tmpfs /tmp:size=64m,mode=1777 myappBacked by RAM, gone when the container stops, and never written to the host’s disk. That last property is the reason to use it: decrypted secrets, session scratch, anything that must not survive in a filesystem someone can later image.
It counts against the container’s memory, so size it. Linux only.
Mount order and shadowing
Mounting over a path hides whatever the image had there:
# The image's /app/config is now invisible
docker run -v conf:/app/config myappIf the volume is empty and you mounted over image content, the container sees an empty directory. Named volumes are the exception: Docker copies the image’s content into a new, empty named volume on first use. Bind mounts and tmpfs never do — they simply shadow.
That asymmetry explains the classic “my config disappeared when I added a bind mount” and is worth being able to state precisely.
The --mount syntax
docker run --mount type=volume,src=db,dst=/data pg
docker run --mount type=bind,src=/srv,dst=/app,ro appMore verbose than -v and explicit about the type, which matters because -v
guesses: a source containing / is a bind mount, anything else is a named
volume. A typo’d host path with -v silently creates a volume named after it
rather than failing.
readonly is worth reaching for by default on anything the container should
only read — config, certificates, source in a production image.
Related
Interview angle 5
- “Volume, bind mount, or tmpfs?” - named volumes are Docker-managed and the right default for persistent data; bind mounts map a host path and suit local development with live reload; tmpfs is memory-only for secrets or scratch that must not touch disk.
- “Why prefer a named volume over a bind mount in production?” - it’s portable, managed by the runtime, avoids host path and permission coupling, and works with volume drivers for networked storage.
- “What happens to a volume when the container is removed?” - it survives. That’s the point, and also why unused volumes accumulate; prune them deliberately, since a stray
docker volume prunecan delete data you wanted. - “What happens if you mount over a directory that has content?” - it is shadowed. A new named volume is the exception: Docker seeds it from the image on first use. Bind mounts and tmpfs never seed, so mounting an empty host directory over
/app/configgives you an empty config directory. - “
-vor--mount?” ---mountin anything scripted.-vinfers the type from whether the source looks like a path, so a mistyped host path silently becomes a named volume instead of an error.