Skip to content

Data & Volumes

Measured on Docker Desktop 4.81.0 / Engine 29.6.1 (macOS, linux/arm64), 2026-09-30.

A container has a writable layer on top of its read-only image layers. Every file you create, modify, or delete inside a running container lives only in that writable layer. The moment the container is removed with docker rm, that layer is gone — permanently.

This is by design. Containers are meant to be stateless, disposable, and reproducible. You should be able to stop a container, throw it away, and start a fresh one from the same image at any time. If your data lived inside the container, that model breaks down.

flowchart TB
  W["Writable layer (this container only): runtime writes, gone on docker rm"]
  I2["Image layer (read-only): COPY app files"]
  I1["Image layer (read-only): node:24-alpine base"]
  W --> I2 --> I1
A container: image layers plus one writable layer

The data is lost on docker rm, not on docker stop. A stopped container keeps its writable layer on disk; docker diff lists exactly what that layer holds:

$ docker run --name eph alpine:3.24 sh -c "mkdir -p /data && echo 'hello world' > /data/message.txt && cat /data/message.txt"
hello world
$ docker diff eph
A /data
A /data/message.txt
$ docker run -d --name keep alpine:3.24 sh -c 'mkdir -p /data; echo persisted > /data/f; sleep 300'
$ docker stop -t 1 keep && docker start keep && docker exec keep cat /data/f
keep
keep
persisted
$ docker rm eph
$ docker run --rm alpine:3.24 sh -c "cat /data/message.txt 2>/dev/null || echo 'File is gone!'"
File is gone!

The file survived the stop/start of the same container and vanished for a new container from the same image. That is why “the container restarted and my data is there” proves nothing about persistence: recreate the container (docker rm + docker run, docker compose up after a config change, an image upgrade) and it is gone.

Docker gives you three mechanisms to break out of the ephemeral writable layer. Each row of the last column was produced on this Mac:

OptionWhere data livesManaged byProof (Engine 29.6.1)
Named volumesDocker-managed directory (inside the VM on Docker Desktop)Dockersurvives docker rm of the container - Named Volumes
Bind mountsAny path on the host filesystemYoufile written in the container appears in the host directory - Bind Mounts
tmpfs mountsMemory only (never written to the container’s disk)Kernel--tmpfs /scratch:size=8m refuses the 9th MB; emptied on restart (below)
$ docker run --rm --tmpfs /scratch:size=8m alpine:3.24 sh -c 'mount | grep scratch; dd if=/dev/zero of=/scratch/big bs=1M count=20; df -h /scratch | tail -1'
tmpfs on /scratch type tmpfs (rw,nosuid,nodev,noexec,relatime,size=8192k)
dd: error writing '/scratch/big': No space left on device
tmpfs 8.0M 8.0M 0 100% /scratch

The 20 MB write stopped at exactly 8 MiB. Note noexec in the mount options: a plain --tmpfs cannot run programs (see the bug below). After docker stop + docker start the same container found /scratch empty while a file written to its normal filesystem was still there. docker inspect shows a tmpfs under .HostConfig.Tmpfs ({"/scratch":""}), not in .Mounts ([]). The long form is --mount type=tmpfs,destination=/scratch,tmpfs-size=8m. tmpfs is for scratch data and secrets you refuse to write to disk; memory it uses counts toward the container’s memory limit.

Before learning how to persist data, it is worth seeing the problem first-hand. The snippet below creates a file inside a container, removes the container, and runs a fresh one — the file is gone.

# Run a container and write a file
docker run --name ephemeral-demo alpine:3.24 sh -c "mkdir -p /data && echo 'hello world' > /data/message.txt && cat /data/message.txt"

# Remove the container
docker rm ephemeral-demo

# Start a brand-new container from the same image
docker run --rm alpine:3.24 sh -c "cat /data/message.txt 2>/dev/null || echo 'File is gone!'"

An AI-written worker script that “keeps the build cache in RAM for speed” and then runs a tool it downloads there:

🐛 Spot the bug

scripts/run-worker.sh
#!/bin/sh
set -eu
# AI: tmpfs = fast scratch space, capped at 8 MB
docker run --rm --tmpfs /work:size=8m alpine:3.24 sh -c '
printf "#!/bin/sh\necho worker ran\n" > /work/tool.sh
chmod +x /work/tool.sh
/work/tool.sh'
Show the answer

The script fails although the file was written and made executable:

$ sh scripts/run-worker.sh
sh: /work/tool.sh: Permission denied # exit code 126

--tmpfs mounts default to noexec,nosuid,nodev (visible in the mount line above), so nothing on that mount can be executed. Add the exec option when a tmpfs really must hold programs:

scripts/run-worker.sh
#!/bin/sh
set -eu
docker run --rm --tmpfs /work:exec,size=8m alpine:3.24 sh -c '
printf "#!/bin/sh\necho worker ran\n" > /work/tool.sh
chmod +x /work/tool.sh
/work/tool.sh'

With :exec the run printed worker ran. Prefer keeping executables in the image and using tmpfs only for data.

A container’s filesystem is an image plus one private, disposable writable layer. Anything you want to outlive the container must be written to something mounted into it: a volume (Docker owns the storage), a bind mount (you own the path), or a tmpfs (RAM, on purpose short-lived). The container’s lifetime and the data’s lifetime are separate decisions.

OptionBenefitCost
Writable layer onlyZero setupLost on docker rm; slower than a volume for heavy writes; grows the container
Named volumeSurvives rm, portable across hosts by name, Docker manages itOpaque location (inside the VM on Desktop) - needs docker run -v or cp to look inside
Bind mountYou see and edit the files with host toolsHost paths, permissions, and OS differences leak into the container
tmpfsNo disk I/O, capped with size=, gone at restartNot durable; counts against memory; noexec unless :exec
  • “docker stop deletes my data.” It does not: the writable layer survives stop/start; only docker rm (or recreating the container) drops it.
  • “The container restarted and my file is still there, so it is persistent.” It is only persistent if it lives on a volume or bind mount; recreation loses it.
  • “tmpfs is just a fast volume.” It is memory, it is emptied when the container stops, and it is noexec by default.
  • “A named volume is a folder on my Mac.” On Docker Desktop it lives inside the VM.
LessonTopic
Named Volumesdocker volume create/ls/inspect/rm, mounting volumes, copy-up, prune semantics
Bind MountsMapping host paths into containers, -v vs --mount, live-editing source code
Volumes vs Bind MountsComparison table, portability, measured write speed, read-only mounts
Backup & RestoreArchiving volumes with a throwaway container, restoring, migrating
What happens to files written inside a container when you run docker rm?
Which storage option is managed entirely by Docker and stored in a Docker-controlled directory?
A tmpfs mount stores data in which location?
You run docker stop web and then docker start web. A file the app wrote to its own filesystem earlier is:
docker run --tmpfs /work alpine:3.24 sh -c '/work/tool.sh' fails with 'Permission denied' although the script is executable. Why?