Volumes, bind mounts & tmpfs
The three ways to attach data.
A container’s writable layer is ephemeral, so anything you want to keep lives on a mount. Docker offers three kinds and choosing correctly is most of the skill. A volume is storage Docker creates and manages in its own area (/var/lib/docker/volumes). A bind mount maps a specific host directory into the container. A tmpfs mount is memory-backed and never touches disk. Same idea — attach storage at a path — three very different lifecycles and use cases.
$ docker volume create pgdata # a managed volume$ docker run -d --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres:16$ docker run -d --mount type=bind,source="$(pwd)"/src,target=/app/src myapp:1.0$ docker run -d --mount type=tmpfs,target=/run,tmpfs-size=64m myapp:1.0
--mount versus -v
The old -v shorthand packs source, target, and options into one colon-separated string — terse, but ambiguous, and it silently creates a host directory when a bind source does not exist. The newer --mount spells out type, source, and target as explicit key/value pairs: more to type, self-documenting, and it errors instead of guessing. Prefer --mount in anything you will read again.
# these do the same thing; the second says what it means and fails loudly on typos$ docker run -v pgdata:/var/lib/postgresql/data postgres:16$ docker run --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres:16
Scenario: a database that must survive redeploys
A Postgres container is recreated on every deploy, and its data must persist across all of them. A named volume is the answer: docker rm removes the container and its writable layer but leaves the volume untouched, so the new container remounts the same data. Volumes are also how you take a consistent backup — mount the volume plus a host directory into a throwaway container and tar it out.
# back up a named volume to a tarball on the host$ docker run --rm \-v pgdata:/data:ro \-v "$(pwd)":/backup \alpine tar czf /backup/pgdata-$(date +%F).tgz -C /data .# restore into a fresh volume by reversing the mounts and untarring
Scenario: live-editing code in development
You want edits on your laptop to appear instantly in the container without rebuilding. A bind mount of your source directory does exactly that. The classic gotcha in Node projects: bind-mounting the whole project shadows the image’s node_modules with your host’s (often empty or wrong-arch). The fix is an anonymous volume on node_modules, which “masks” the bind mount for that subpath and keeps the image’s installed dependencies.
$ docker run -d \--mount type=bind,source="$(pwd)",target=/app \--mount type=volume,target=/app/node_modules \ # keep the image’s depsmyapp:dev# host source is live-mounted; node_modules stays the container’s own
Scenario: secrets that must never hit disk
A process needs a credential file at runtime but you do not want it written to the node’s disk where it could linger or be backed up. A tmpfs mount is memory-backed: the path exists inside the container, lives in RAM, and vanishes the instant the container stops — nothing to recover from disk afterward. It is also useful for scratch and cache directories that should not bloat the writable layer.
$ docker run -d --mount type=tmpfs,target=/secrets,tmpfs-size=8m,tmpfs-mode=0700 myapp:1.0$ docker exec <id> sh -c 'mount | grep /secrets'tmpfs on /secrets type tmpfs (rw,size=8192k,mode=700) # RAM-backed, gone on stop