How Docker volume lifecycles work, why --volumes is dangerous, and how to protect persistent data.
You remove and rebuild your Compose app:
docker compose down
docker compose up -d --build
The containers are brand new, the image is rebuilt, and yet your database, accounts, and settings are all still there.
Is Docker failing to clean up? No. This is exactly how persistent storage is designed to work.
1. Containers and volumes have different lifecycles
A container is disposable. A volume is built to outlive it.
services:
app:
image: alpine:3.20
command: ["sleep", "infinity"]
volumes:
- app-data:/data
volumes:
app-data:
Here app-data is a named volume. Compose creates it and mounts it at /data. When the container is removed, the volume stays on the Docker host.
This is what makes databases, uploads, and configuration survive upgrades.
2. What does docker compose down remove?
docker compose down |
docker compose down --volumes |
|
|---|---|---|
| Containers | Removed | Removed |
| Compose-managed networks | Removed | Removed |
Named volumes (declared in top-level volumes) |
Kept | Removed |
| Anonymous volumes | Kept (not reattached by the next up) |
Removed |
external: true volumes |
Kept | Kept |
| Host bind mounts | Kept | Kept |
Both commands remove containers. The only difference is whether Compose also deletes its persistent storage.
Warning:
down --volumes(or-v) permanently deletes that data.
3. Verify it yourself
Start the app and write a file into the volume:
docker compose up -d
docker compose exec app sh -c 'echo "Persistent data!" > /data/check.txt'
Remove and recreate the app:
docker compose down
docker compose up -d
docker compose exec app cat /data/check.txt
Output:
Persistent data!
Now remove the volumes too:
docker compose down --volumes
docker compose up -d
docker compose exec app cat /data/check.txt
This time you get No such file or directory: the old volume was deleted and up created a new, empty one.
This is why rebuilding an image never resets your database. Images and volumes are separate things.
4. A safer approach: external: true
For data you cannot afford to lose, manage the volume outside the Compose lifecycle.
Create it once:
docker volume create production-data
Then reference it:
services:
app:
image: alpine:3.20
command: ["sleep", "infinity"]
volumes:
- app-data:/data
volumes:
app-data:
external: true
name: production-data
With external: true, Compose treats the volume as an existing resource it does not own:
-
docker compose down --volumeswill not delete it. - If the volume does not exist,
docker compose upfails with an error instead of silently creating an empty one. That is a feature: a typo in the name will not give you a blank database.
Note that external: true protects against Compose, not against everything. docker volume rm and docker volume prune can still delete the volume, and it is not a backup.
5. Common commands at a glance
| Command | Removes containers | Removes Compose-managed named volumes |
|---|---|---|
docker compose stop |
No | No |
docker compose down |
Yes | No |
docker compose up -d --build |
Recreates when needed | No |
docker compose down -v |
Yes | Yes |
Bind mounts (./data:/var/lib/app) are a different mechanism. Compose never deletes the host directory.
6. Production checklist
Before updating or removing any stateful stack:
- List volumes with
docker volume ls. - Check what each container actually mounts with
docker inspect <container>. - Keep deployment (the Compose file) and data (the volume) separate where possible.
- Keep independent, tested backups of databases and application data.
- Never use
down -vas a routine update command.
Pitfall: changing the project name
Compose prefixes managed volume names with the project name, so app-data becomes myproject_app-data. If you rename the project directory or set a different -p / name:, Compose creates a new, empty volume and your data appears to vanish. The old volume still exists; find it with:
docker volume ls
Mount it again by restoring the original project name, or reference it explicitly with external: true.
Final thoughts
A container is not your data.
- Use
docker compose downto remove the stack and keep its volumes. - Use
docker compose down -vonly when you deliberately want to destroy Compose-managed data. - Use
external: truewhen storage should have its own lifecycle.
Knowing the difference can save you from one of the most expensive mistakes in container administration: deleting production data with a single flag.
Top comments (0)