4.5 KiB
Lesson 06: Volumes and Persistence
By default, containers are ephemeral. Whatever a container writes to its own filesystem disappears the moment that container is removed. That's a feature, not a bug — it's what keeps containers clean — but it's a surprise the first time you run a database in a container and then docker rm it.
See the problem
docker run -it --name scratch ubuntu bash
Inside:
echo "important data" > /important.txt
cat /important.txt
exit
Now remove the container and try again with a fresh one:
docker rm scratch
docker run -it --name scratch ubuntu bash
cat /important.txt # No such file or directory
exit
docker rm scratch
The file existed only inside the old container's writable layer. New container, new layer, no file.
Two ways to persist data
Both are called "mounts" — they connect a path inside the container to something that survives the container.
1. Bind mounts — connect to a folder on your host
A bind mount maps a folder on your real machine to a folder inside the container. Changes show up on both sides instantly.
mkdir ~/docker-data
docker run -it --name scratch \
-v ~/docker-data:/data \
ubuntu bash
The flag -v <host-path>:<container-path> is the mount. Inside the container:
echo "important data" > /data/important.txt
exit
Back on your host:
cat ~/docker-data/important.txt
The file is there, on your real machine. Remove and re-create the container — the file is still there because it never lived inside the container in the first place.
Bind mounts are great for:
- Development — mount your source code into the container so edits on your laptop are picked up immediately.
- Configs — point the container at a config file you maintain on your host.
- "I need to see the files" — folders you want to open in your file manager.
On Windows, host paths look like
C:\Users\you\docker-dataor, in PowerShell,${PWD}\docker-data. On Mac/Linux,~/docker-dataor$(pwd)/docker-dataworks.
2. Named volumes — Docker-managed storage
A named volume lives somewhere Docker chooses (you don't need to care where), and you refer to it by name.
docker volume create mydata
docker run -it --name scratch \
-v mydata:/data \
ubuntu bash
Inside the container, write to /data as before, exit, remove the container, run a fresh one with the same -v mydata:/data, and your files are still there.
Named volumes are better than bind mounts when:
- You don't care where on disk the data lives — you just want it to persist.
- The data is "the database's data" or "the model's cache" — internal to the app, not something a human is going to open.
- You're running on a server and don't want to commit to specific host paths.
List your volumes:
docker volume ls
Remove a volume (only when you're sure):
docker volume rm mydata
A realistic example: persisting a database
docker run -d \
--name pg \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16
Run that, connect, create a database, write some rows, then:
docker stop pg
docker rm pg
Now run it again with the same -v pgdata:/var/lib/postgresql/data. Your database is still there. The volume outlived the container.
Without that volume, removing the container would have erased everything.
The danger to know about
When you remove a stopped container with docker rm, the volume survives. Good.
When you bring down a Compose stack (lesson 09) with docker compose down, volumes survive. Good.
When you bring it down with docker compose down -v, the -v is "and also nuke the volumes." That command will silently delete your database data. down -v is destructive. Use it deliberately.
Likewise:
docker volume prune
Removes any volume that no container is currently using. Convenient for cleanup; catastrophic if your database container happened to be stopped at the time.
Try it yourself
- Use a bind mount to share your current directory with a Python container:
docker run -it -v "$(pwd)":/work -w /work python:3.12 bash. Thenls /workinside the container — you should see the same files as on your host. - Edit a file on your host (in any editor) while that container is running, then
catit from inside the container. The change is instant. - Move on to
07_ports_and_env.mdwhere we'll let containers talk to the outside world.