Chapter 6: Storage
Topic 34

Volumes vs Bind Mounts vs tmpfs

VolumesMounts

Docker gives you three ways to put data somewhere other than the disposable writable layer, and choosing the wrong one is the source of most storage pain. A named volume is Docker-managed storage under /var/lib/docker/volumes — the default for data you care about. A bind mount maps an exact host path into the container — ideal for development, leaky for production. A tmpfs mount lives only in RAM and never touches disk — for secrets and scratch.

This page is the spine of the chapter. The next four take each of these apart in depth, so the goal here is the decision: what defines each mount type, how the flag you type actually selects one, and which job each is the right answer to. Get the distinction clear once and the rest of the chapter is detail.

Named Volumes — Docker-Managed Storage

A named volume is storage Docker creates and owns, living under /var/lib/docker/volumes/<name>/_data and decoupled from any specific host path. You refer to it by name — driftwood-db-data — not by where it sits on disk, and Docker handles the rest. It survives container removal, so the container is disposable while the data is not. For persistent application data like Driftwood's Postgres directory, a named volume is the portable, recommended default, and the one you reach for unless you have a specific reason not to.

Bind Mounts — A Host Path Mapped In

A bind mount takes an exact absolute host path and mounts it straight into the container, so both sides see the same files live. Edit a file on the host and the container sees the change immediately, with no rebuild and no copy — which makes bind mounts the backbone of containerized development. The cost is portability: a bind mount ties the container to that one machine's directory layout, and it inherits the host's file ownership and permissions, so the same command behaves differently on a different host. Good for editing source, sharing a config; wrong as a general persistence mechanism.

tmpfs — Memory Only, Never on Disk

A tmpfs mount lives in the host's memory rather than on the container's filesystem, so its contents are never written to a volume or host path and vanish the moment the container stops. That is not a limitation to work around — it is the entire point. The use case is sensitive scratch: a decrypted secret, a session cache, a token you specifically do not want sitting on disk where it could survive in a backup or a forensic image. (One caveat: under memory pressure the kernel can still page tmpfs data to swap, so it isn't an absolute guarantee that nothing ever touches the disk.) If the data must outlive the process, tmpfs is the wrong tool; if you want to keep data off the durable filesystem, it is the right one.

Three places to put data, and what each is for
Volume
Docker-managed storage under /var/lib/docker, portable and decoupled from any host path — the default for data you care about.
Bind mount
An exact host path mapped straight in, so both sides see the same files live — great for development.
tmpfs
Memory only, never on disk; contents vanish when the container stops — for secrets and scratch.

The Mount Syntax, Read in Words

The short -v flag packs the mount type into punctuation, and reading it correctly matters. -v name:/path names a volume. -v /abs/host/path:/path is a bind mount — the leading slash on the source is the tell, because it makes the source a host path instead of a volume name. --tmpfs /path is a memory mount. The modern --mount type=volume|bind|tmpfs,... form spells the type out as a key, which is harder to get wrong and worth the extra characters once a container has more than one or two mounts.

Three mounts on one run — a named volume, a read-only bind mount, and a tmpfs
docker run -d --name driftwood-db \
  -v driftwood-db-data:/var/lib/postgresql/data \
  -v /home/dev/driftwood/postgres.conf:/etc/postgresql/postgresql.conf:ro \
  --tmpfs /tmp \
  postgres:16

# the explicit --mount form says the same thing, unambiguously:
docker run -d --name driftwood-db \
  --mount type=volume,src=driftwood-db-data,dst=/var/lib/postgresql/data \
  --mount type=bind,src=/home/dev/driftwood/postgres.conf,dst=/etc/postgresql/postgresql.conf,ro \
  --mount type=tmpfs,dst=/tmp \
  postgres:16

Both invocations mount the same three things. The first leans on the leading-slash convention to distinguish the named volume from the bind mount; the second names each type outright. The :ro suffix and the ro key both make the bind mount read-only — the container can read the config but not overwrite the host's copy.

Picking the Right One

The decision collapses to a short rule. Durable application data goes on a named volume. Live source code in development goes on a bind mount. Secrets and ephemeral scratch go on a tmpfs. The writable layer (topic 33) is the right home for none of these — only for truly throwaway state you would never miss. When in doubt, default to a named volume and step outside it only for the specific reasons bind mounts and tmpfs exist.

Volume vs Bind Mount vs tmpfs

Named volume-v driftwood-db-data:/var/lib/postgresql/data. Docker-managed storage under /var/lib/docker/volumes, portable and lifecycle-independent of the container. Choose it for data you keep, like a database.

Bind mount-v /home/dev/driftwood:/app. Maps an exact host path straight in. Choose it to live-edit source in development or share a host config, accepting that it leaks the host's directory layout and file ownership.

tmpfs--tmpfs /run/secrets. Lives in RAM only and never persists. Choose it for secrets or scratch you don't want on disk. Volume for durable data, bind mount for dev, tmpfs for in-memory secrets.

Common Mistakes
  • Reaching for a bind mount to hold production database data because it "feels simpler" — you pin the container to one host's exact path and inherit host file ownership and permissions, where a named volume would be portable and Docker-managed.
  • Writing a leading-slash host path where you meant a named volume (-v /data:/app instead of -v data:/app) — Docker silently creates the host directory and bind-mounts it, so you get an empty mount and a missing volume instead of an error.
  • Putting secrets in a named volume or a bind mount and assuming they're protected — both persist on disk; a tmpfs keeps the data in memory rather than writing it to a volume or host path that would survive in a backup (though under memory pressure the kernel can still page it to swap).
  • Expecting tmpfs contents to survive a container restart — they don't; tmpfs is RAM-backed and wiped when the container stops, so it's wrong for anything that must outlive the process.
  • Mixing -v short syntax across a large compose.yaml until nobody can tell a volume from a bind mount at a glance — the ambiguity is exactly where the wrong-mount-type bugs hide.
Best Practices
  • Default to a named volume for any data the application must keep, and step outside it only for the specific reasons bind mounts and tmpfs exist.
  • Use a bind mount only when you specifically need the host's real files — live-editing source in dev, or sharing a host-managed config like Driftwood's nginx file — not as a generic persistence mechanism.
  • Mount a tmpfs for decrypted secrets and ephemeral scratch, so sensitive bytes stay in RAM and never land on disk or in a backup.
  • Prefer the explicit --mount type=... syntax in anything non-trivial, so the mount kind is unambiguous and a typo can't silently turn a volume into a host bind.
Comparable tools Podman the same three mount types with identical -v/--mount flags and volume semantics Kubernetes the distinction maps to PersistentVolume vs hostPath vs emptyDir/Memory (Ch12 topic 76) mount --bind a plain Linux bind mount is exactly what a Docker bind mount is underneath

Knowledge Check

What is the one defining property of a named volume?

  • It is Docker-managed storage referenced by name and decoupled from any specific host path
  • It maps an exact host directory path you choose directly into the running container, so both the host and the container see and edit the very same files live on disk
  • It lives in RAM only and is wiped the moment the container stops or restarts
  • It is destroyed along with the container's writable layer when you run docker rm

In docker run -v /data:/app, what does the leading slash on the source actually select?

  • A bind mount of the host directory /data — the leading slash marks it as a host path, not a volume name
  • A named volume literally called /data, created and managed by Docker under /var/lib/docker/volumes with the slash kept as part of the volume's own name
  • A tmpfs mount, because the leading slash signals a RAM-backed in-memory path
  • Nothing valid — Docker rejects a leading-slash source as a hard syntax error

Which mount type should hold a decrypted secret you don't want written to disk?

  • A tmpfs mount, because it is RAM-backed and never reaches persistent storage
  • A named volume, since Docker manages and supposedly protects its contents from exposure
  • A bind mount, so the decrypted secret lives in a known, inspectable host directory
  • The container's writable layer, since it is discarded on removal anyway

When is a bind mount the right choice over a named volume?

  • When you specifically need the host's real files live — editing source in dev or sharing a host config
  • For a production database's data directory, because pinning it to a fixed, well-known host path makes the files simpler to locate, inspect, and back up directly from the host
  • When you need the sensitive data to stay off disk entirely, never persisted
  • When you want the same mount to be portable across many different hosts

You got correct