Chapter 116·Intermediate·8 min read
Volumes & Persisting Data
A plain-English guide to data in Docker — why a container's filesystem is temporary, the difference between named volumes and bind mounts, how to keep a database's data alive across restarts, and how bind-mounting your source enables live-reload development. Making data outlive the container.
July 18, 2026
Chapter two gave you a superpower: containers are disposable. Delete one and start a fresh, clean instance from the same image in milliseconds. That's perfect — until you remember that real applications have data they can't afford to lose. A database's records, a user's uploaded files, an app's accumulated state: none of that should evaporate just because a container restarted. This chapter resolves the tension. It's the last piece of the local-development picture, and it's where Docker meets the messy, stateful real world.
The problem: a container's memory is temporary
Recall how a container works: it's a read-only image plus a thin writable layer on top, where the running app makes its changes. That writable layer is the catch. It is tied to that specific container, and when the container is removed, the layer — and everything written to it — is destroyed with it.
For a lot of software, that's completely fine. A web server that just handles requests and holds no important local state can be thrown away and recreated freely; that disposability is a feature. But now picture running a database inside a container. It writes your data to its filesystem — which is that ephemeral writable layer. Stop and remove the container to upgrade it, and every record is gone. The very disposability that makes containers wonderful for stateless apps is a disaster for anything that needs to remember.
The mental shift: stop thinking of data as living inside a container. Instead, the container is a disposable engine, and the data lives in durable storage that you attach to it. Swap the container; keep the storage.
The solution: storage that lives outside the container
Docker gives you two ways to attach outside storage to a container. Both make a location's data survive the container's death, but they serve different purposes, and knowing which to reach for is the whole skill here.
The two mechanisms are named volumes and bind mounts. Let's take them one at a time.
Named volumes: Docker-managed persistence
A named volume is a chunk of persistent storage that Docker creates and manages for you, living in an area on the host that Docker controls — deliberately separate from any container. You give it a name, attach it to a container at a particular path, and anything the container writes to that path goes into the volume instead of the disposable writable layer.
You attach one with the -v flag, written volume-name:/path/inside/container:
$ docker run -d \
-v pgdata:/var/lib/postgresql/data \
postgres:16That runs PostgreSQL and points its data directory (/var/lib/postgresql/data, where Postgres keeps everything) at a named volume called pgdata. Now the data lives in pgdata, outside the container. Stop the container, remove it, start a brand-new one attached to the same pgdata volume — and every table and row is exactly where you left it. The container was disposable; the data wasn't.
$ docker volume ls # list your volumes
$ docker volume rm pgdata # delete one (and its data — carefully!)Bind mounts: a window into your own folder
A bind mount is the other option, and it's conceptually different: instead of Docker-managed storage, it maps a specific folder on your host machine directly into the container. The container sees your folder as one of its own directories, and — crucially — changes on either side are instantly visible on the other. It's a live, two-way window between host and container.
The syntax looks similar but the left side is now a path on your machine rather than a volume name:
$ docker run -d \
-v /Users/raghav/project:/app \
myappThis mounts your local project folder into the container at /app. Whatever is in your folder appears inside the container immediately; whatever the container writes to /app appears in your folder. The data plainly persists — it's literally sitting in your normal filesystem — but persistence isn't really the point of a bind mount. Its superpower is live syncing, which unlocks a genuinely lovely development workflow.
The killer use: live-reload development
Here's where bind mounts shine. Think back to the Dockerfile chapter: normally, changing your code means rebuilding the image to get the new code inside. During active development, rebuilding on every edit would be maddening. A bind mount removes that entirely.
Bind-mount your source code into the container, and your editor on the host writes straight through into the running container. Combined with a dev server that watches for file changes (most frameworks have one), the flow becomes: save a file in your editor → the change is instantly inside the container → the dev server reloads → you see it in the browser. No rebuild, no restart, per edit.
This is how most people actually develop with Docker: bind-mount the source for a tight edit-reload loop, and let the container provide the consistent environment underneath. You get the "runs the same everywhere" guarantee and the instant feedback of local editing.
A note on production
One honest caveat, since habits formed now carry forward. Bind mounts are a development convenience — they tie a container to a specific folder on a specific machine, which is exactly what you don't want in production, where a container might run on any server. In production you use named volumes (or the cloud provider's managed storage) for persistent data, and you bake the code into the image rather than mounting it. So the pattern is: bind-mount source for local development, named volumes for durable data everywhere, and never bind-mount code into a production container. Keeping that straight saves real confusion later.
The local picture is complete
With volumes, you've closed the last gap in running containers on your own machine. You can package an app into an image, run it as a disposable container, and now keep its important data alive across restarts and rebuilds — databases, uploads, and state all survive, while the containers themselves stay cheap and replaceable. That's a genuinely complete toolkit for containerised development.
But every example so far has been a single container in isolation. Real systems are rarely one container — they're a web app and a database and a cache, all needing to find and talk to each other. Tomorrow the guide turns to exactly that: how containers network together, how Docker Compose describes a whole multi-container app in one file, how images get shared through registries, and what changes when containers go to production. The single-container foundation you've built over these four chapters is what all of it stands on.