1. The Ephemeral Filesystem
Every container gets a thin, writable layer on top of its (read-only) image layers.
Anything the process writes — log files, an uploaded image, a SQLite database —
lands in that writable layer. The moment you run docker rm, that layer
is gone permanently.
docker run -it --name scratch alpine sh
# inside the container:
echo "important data" > /data.txt
exit
docker start -ai scratch # restart the SAME container — data.txt is still there
docker rm -f scratch # remove it entirely
docker run -it --name scratch alpine sh
cat /data.txt # No such file — it's genuinely gone, this is a brand new container
Notice the distinction: stopping and restarting the same container preserves its writable layer — the container still exists, just paused. Removing the container destroys that layer for good. This is exactly the behavior you want for a stateless web server (throw it away, start a fresh one, no problem) and exactly the behavior that will delete your production database if you don't do something about it.
Disposability is what makes containers so easy to reason about — you should be able to docker rm -f any stateless container and recreate it identically. The fix for stateful data isn't "avoid removing containers," it's "put the data somewhere that isn't the container's writable layer" — which is exactly what volumes are for.
2. Named Volumes
A named volume is storage managed entirely by Docker, living outside any single container's lifecycle. It's the recommended way to persist data for most stateful workloads, because Docker handles where it physically lives.
docker volume create my-data
docker volume ls
docker volume inspect my-data
docker run -d --name app1 -v my-data:/app/data alpine sleep 3600
# -v my-data:/app/data mount the "my-data" volume at /app/data inside the container
docker rm -f app1 # remove the container...
docker run -d --name app2 -v my-data:/app/data alpine sleep 3600
# ...the volume, and its data, survives and is now attached to app2
You can also mount a volume into multiple containers simultaneously — a common pattern for sharing files between a web server and a background worker, or for a sidecar container that processes logs written by the main one.
docker volume ls # list all volumes
docker volume rm my-data # delete a volume (fails if it's in use by a container)
docker volume prune # delete ALL unused volumes — use carefully
A named volume isn't backed up automatically — it's still just a directory on the host (or a volume driver's backend). Losing the host or accidentally running docker volume prune loses the data. Real production databases still need a separate backup strategy on top of volumes.
3. Bind Mounts & tmpfs
A bind mount maps a specific path on your host directly into the container — unlike a named volume, Docker doesn't manage it, you point at an exact host directory. This is the tool you reach for during local development, so code edits on your host show up instantly inside the running container:
docker run -d --name dev-app \
-v $(pwd)/src:/app/src \
-p 3000:3000 \
my-app:dev
# Edit a file in ./src on your host — the running container sees the change
# immediately, since it's literally the same files on disk, not a copy.
docker run -d --tmpfs /app/cache my-app:1.0
# tmpfs mounts live ONLY in memory — never written to disk, gone the instant
# the container stops. Useful for secrets you don't want touching disk, or
# scratch space that needs to be fast and doesn't need to survive a restart.
Named volume # production data (databases, uploads) — Docker-managed, portable
Bind mount # local development — live host-file editing, host-path-specific
tmpfs # ephemeral, sensitive, or performance-critical scratch data — memory only
A bind mount path like C:\Users\you\project\src or /home/you/project/src only exists on your exact machine — it can't be part of a portable, shareable image or Compose file that a teammate or a production server can just run. That's exactly why named volumes, not bind mounts, are the production answer.
4. A Real Database, Persisted
Let's run PostgreSQL correctly — the way that survives a container restart, a container removal, and an image upgrade:
docker volume create pg-data
docker run -d \
--name my-postgres \
-e POSTGRES_PASSWORD=devpassword \
-e POSTGRES_DB=appdb \
-v pg-data:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16
# Connect and create some data
docker exec -it my-postgres psql -U postgres -d appdb -c \
"CREATE TABLE notes (id serial PRIMARY KEY, body text); INSERT INTO notes (body) VALUES ('hello');"
# Prove persistence: remove and recreate the container against the SAME volume
docker rm -f my-postgres
docker run -d --name my-postgres -e POSTGRES_PASSWORD=devpassword \
-v pg-data:/var/lib/postgresql/data -p 5432:5432 postgres:16
docker exec -it my-postgres psql -U postgres -d appdb -c "SELECT * FROM notes;"
# -> your row is still there
The key detail is knowing which path inside the image actually needs to be
persisted — for Postgres it's /var/lib/postgresql/data, documented on
every official database image's Docker Hub page. Mount a volume anywhere else and
you'll lose data exactly the same as if you'd used no volume at all.
This same problem — "how does a Pod, which is just as disposable as a container, keep its data?" — reappears in Week 11 as PersistentVolumes and PersistentVolumeClaims. The concept transfers directly: name the storage, mount it at the right path, and it survives the workload being recreated.
5. Hands-on Exercise
Run PostgreSQL two ways, and prove which one survives
Deliberately reproduce a data-loss bug, then fix it with a named volume, so the difference is something you've seen rather than just read about.
Part 1 — Reproduce the data loss:
- Run
docker run -d --name broken-db -e POSTGRES_PASSWORD=devpw postgres:16— deliberately with no volume mounted. - Connect with
docker exec -it broken-db psql -U postgresand create a table with a few rows of data. - Remove the container:
docker rm -f broken-db, then start a brand new one with the exact same command. - Try to query your table again — confirm it's gone, and write one sentence explaining exactly why, in terms of what actually got deleted.
Use \dt inside the psql prompt to list tables quickly, and \q to exit back to your shell.
Part 2 — Fix it, and prove the fix under a harder scenario:
- Create a named volume and rerun Postgres correctly, mounted at
/var/lib/postgresql/data, recreating your table and data. - Remove the container and recreate it against the same volume — confirm the data survives, same as the walkthrough above.
- Now go further: run
docker volume inspecton your volume and find the actual host path where Docker is storing the data (the "Mountpoint" field). Note it down. - Finally, simulate an image upgrade: remove the container and recreate it using
postgres:15instead ofpostgres:16, still pointed at the same volume. Does your data still show up? What does this tell you about how tightly (or loosely) a named volume is coupled to a specific image version?
A volume is just files on disk — it doesn't know or care which image wrote them, which is exactly why real database version downgrades can be dangerous even though the volume "just works" for an upgrade. Data format compatibility across versions is your problem, not Docker's.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why does data written inside a container disappear after docker rm, but not after docker stop?
Why does data written inside a container disappear after docker rm, but not after docker stop?
docker stop pauses the container but keeps its writable layer intact on disk — starting it again with docker start resumes with the same data. docker rm deletes the container object entirely, including its writable layer, so any data that lived only there is gone permanently.
Q2
You're setting up local development with live-reload on code edits. Named volume or bind mount?
You're setting up local development with live-reload on code edits. Named volume or bind mount?
Bind mount. It maps an exact host directory into the container, so edits made in your editor on the host appear inside the running container immediately — exactly what live-reload needs. Named volumes are Docker-managed and better suited to portable, production data rather than pointing at a specific local source folder.
Q3
What's the defining property of a tmpfs mount?
What's the defining property of a tmpfs mount?
It exists only in memory and is never written to disk — the data vanishes the instant the container stops. It's useful for sensitive scratch data you don't want persisted anywhere, or for performance-sensitive temporary files, but it offers zero persistence, even across a simple restart.
Q4
Why does the exact mount path matter when persisting a database's data with a volume?
Why does the exact mount path matter when persisting a database's data with a volume?
The database only writes its actual data files to one specific directory inside the image (for Postgres, /var/lib/postgresql/data). Mounting a volume anywhere else persists nothing useful — the database still writes its real data to the container's disposable writable layer, and it's lost exactly as if no volume existed at all.