Everything a container writes goes to its thin read-write layer, and that layer is deleted with the container (lesson 5). If data needs to outlive the container, it has to live somewhere else on the host. Docker gives you three somewhere-elses.
Topic 1: The Three Options
| Volume | Bind mount | tmpfs | |
|---|---|---|---|
| Lives at | /var/lib/docker/volumes/, managed by Docker | Any path you choose on the host | RAM only |
| Managed by | Docker | You | The kernel |
| Survives container deletion | Yes | Yes | No |
| Survives host reboot | Yes | Yes | No |
| Portable across hosts | With a volume driver | No — depends on host layout | N/A |
| Right for | Application and database data | Source code in dev, host config, sockets | Secrets, scratch space |
The short version of the guidance: volumes for data, bind mounts for development, tmpfs for things that must never touch disk.
Topic 2: Bind Mounts
The oldest mechanism. You pick a host path and map it into the container.
# -v form: host:container
docker run -it --name dev -v /home/ubuntu/test:/app alpine /bin/sh
# --mount form: explicit, and the one to prefer
docker run -it --name dev \
--mount type=bind,source=/home/ubuntu/test,target=/app \
alpine /bin/sh
# read-only, so the container cannot modify your host files
docker run -it \
--mount type=bind,source=/home/ubuntu/test,target=/app,readonly \
alpine /bin/sh
-v and --mount do the same job. --mount spells out every option, fails loudly on a typo, and is what the documentation now prefers. -v is terser and universal in older examples — you need to read both.
Two behaviours specific to bind mounts:
The host path wins. If the container image had files at /app and you bind-mount over it, the image’s files are hidden — not merged, not deleted, just shadowed for the life of the mount.
With -v, a missing host path is created as an empty directory (owned by root). With --mount, a missing source is an error. That is one of the better arguments for --mount: a typo in a host path silently gives you an empty directory rather than your data.
Bind mounts are excellent for development — mount your source into the container and edit on the host with live reload — and a liability in production, because they couple the container to a specific host’s directory layout and let the container write anywhere you point it. A bind mount of / or /var/run/docker.sock is a full host compromise (lesson 12).
Topic 3: Volumes
Volumes are created and managed by Docker. You refer to them by name and never care where they physically live.
docker volume create pgdata
docker volume ls
docker volume inspect pgdata # shows Mountpoint under /var/lib/docker/volumes/
docker volume rm pgdata
docker volume prune # remove all volumes not attached to a container
docker run -d --name db \
--mount source=pgdata,target=/var/lib/postgresql/data \
postgres:16
# equivalent -v shorthand
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16
Note how -v decides what you meant: a path starting with / is a bind mount; anything else is a volume name. -v /data:/app and -v data:/app are entirely different operations distinguished by one character. Another reason --mount is worth the extra typing.
Volumes are shareable and survive everything:
docker run -d --name alp1 --mount source=myvol,target=/test alpine sleep 1d
docker exec alp1 touch /test/1.txt
docker rm -f alp1
docker volume ls # myvol is still there
docker run -d --name alp2 --mount source=myvol,target=/test alpine sleep 1d
docker exec alp2 ls /test # 1.txt
# two live containers, one volume
docker run -d --name alp3 --mount source=myvol,target=/test alpine sleep 1d
docker exec alp3 touch /test/2.txt
docker exec alp2 ls /test # 1.txt 2.txt
That last block is the sharing case. Note that Docker does no coordination whatsoever — two containers writing the same file will corrupt it exactly as two processes would. Sharing works for a producer and a consumer; it does not make a filesystem concurrent-safe.
One more property that matters: when you mount an empty volume onto a path that has content in the image, Docker copies the image’s content into the volume on first use. That is how docker run -v conf:/etc/nginx nginx gives you a volume pre-populated with nginx’s config. Bind mounts do not do this — they shadow. It is a genuine behavioural difference and it catches people.
Topic 4: Anonymous Volumes and the VOLUME Instruction
A Dockerfile can declare a volume:
FROM tomcat:10-jdk21
VOLUME /usr/local/tomcat/logs
EXPOSE 8080
If you start a container from that image without mounting anything at that path, Docker creates an anonymous volume — a real volume with a 64-character hex name and no other identity.
docker run -d --name app myimage
docker volume ls
# DRIVER VOLUME NAME
# local 8f2c1b9e4a...d31 ← anonymous, created for you
This is where hosts quietly fill up. Anonymous volumes:
- are created automatically, without you asking;
- are not removed when the container is removed (unless you use
docker rm -vor ran with--rm); - accumulate one per container start, forever;
- are effectively unidentifiable after the fact.
Two habits fix it. Mount a named volume at that path so no anonymous one is created, and run docker volume ls periodically to see what has accrued.
The broader guidance: prefer mounting explicitly at run time over VOLUME in the Dockerfile. VOLUME also has a subtle build-time effect — any RUN that writes to a declared volume path after the VOLUME instruction has its changes discarded, which produces genuinely baffling bugs.
Topic 5: tmpfs
A tmpfs mount lives in the host’s RAM. It never touches disk and it vanishes when the container stops.
docker run -d --name t1 --tmpfs /app alpine sleep 1d
docker run -d --name t2 --mount type=tmpfs,destination=/app,tmpfs-size=64m alpine sleep 1d
Use it for:
- Secrets written to a file at runtime that must not be recoverable from disk.
- Scratch directories for a process that insists on writing temp files at high volume.
- A read-only root filesystem’s writable holes — the standard pattern is
--read-onlyplus--tmpfs /tmp, so the container can still write where it needs to but nowhere else (lesson 12).
Always set tmpfs-size. An unbounded tmpfs consumes host RAM, and RAM exhaustion on the host is worse than a full disk.
Topic 6: Backups, Drivers and the Stateful Question
Docker gives you volumes. It does not give you backups — that gap is one of Docker’s honest weaknesses. The standard pattern is a throwaway container with both the volume and a host directory mounted:
# back up
docker run --rm \
-v pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
# restore
docker run --rm \
-v pgdata:/data \
-v "$PWD":/backup \
alpine sh -c 'cd /data && tar xzf /backup/pgdata-2026-08-17.tar.gz'
For a database, prefer the database’s own dump tool over a filesystem tar of a live data directory — a tar of files being written is a backup you have not tested and will not enjoy restoring.
Volume drivers move volumes off the local disk entirely. Plugins exist for NFS, cloud block storage, and vendor systems like NetApp or vSphere. This is what makes a volume portable across hosts, which local volumes are not:
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=10.0.0.5,rw \
--opt device=:/exports/appdata \
appdata
Which brings back the stateful question from lesson 1, now with the mechanism visible. Running a stateful application in a container is fine when you can answer three things: which exact path holds the data, which volume backs that path, and how you restore it. If any of those is unclear, you have a container that looks fine right up until it does not.
Try it yourself: Start a Postgres container without mounting anything, create a table, remove the container, start a fresh one. The table is gone and you have just demonstrated the whole lesson. Then repeat with -v pgdata:/var/lib/postgresql/data and watch it survive.
Common mistake: Running docker system prune -a --volumes for a disk-space problem. It removes unused volumes, and “unused” means “no container currently attached” — which includes the database volume whose container you stopped an hour ago. Run docker volume ls and docker system df -v first; prune volumes deliberately, never as part of a general cleanup reflex.