Containers are meant to be thrown away. Volumes are where you put the things you don’t want thrown away with them. The interview question isn’t really “what is a volume” but “when does the data disappear”, and the answer depends on which kind of volume you have and which command you ran.
All the commands below were run on Docker Engine with the local volume driver and an alpine:3.20 image. Container and volume names are shortened for readability.
A named volume survives the container
Create one, write to it from one container, read it from another:
docker volume create data
docker run --rm -v data:/data alpine:3.20 sh -c 'echo v1 > /data/state.txt'
docker run --rm -v data:/data alpine:3.20 cat /data/state.txtv1The first container was removed the moment it exited (--rm), yet the file is still there. Docker manages the storage location:
$ docker volume inspect data --format '{{.Driver}} {{.Mountpoint}}'
local /var/lib/docker/volumes/data/_dataOn Linux that path is on the host. On Docker Desktop for Mac or Windows it’s inside the Linux VM, so you won’t find it in Finder, which is a frequent source of confusion. Either way, don’t edit files there directly; go through a container.
A volume that’s attached to a container, running or stopped, can’t be removed:
$ docker volume rm data
Error response from daemon: remove data: volume is in use - [5c2eb71e2f69...]That message is a safety net. If you see it during cleanup, find the container with docker ps -a --filter volume=data before deciding anything.
Anonymous volumes and how they pile up
An anonymous volume is one Docker names for you with a 64-character hex ID. You get one when you write -v /cache with no name, or when the image declares VOLUME /cache and you don’t mount anything there. Many official images do this: Postgres declares /var/lib/postgresql/data, for instance.
Here’s a test image with a VOLUME instruction:
FROM alpine:3.20
VOLUME /cache
CMD ["sh", "-c", "date > /cache/t"]Running it without --rm creates and keeps an anonymous volume:
$ docker run --name v2 volimage
$ docker inspect v2 --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{end}}'
volume dc3ca4e841095605be35a39e6ff126bf343419ecbcd3d527a6d9ca92d81657b0 -> /cacheWhat happens next depends on how you remove things:
| Action | Named volume | Anonymous volume |
|---|---|---|
docker stop / docker start |
kept | kept |
docker rm |
kept | kept, now orphaned |
docker rm -v |
kept | deleted |
docker run --rm (on exit) |
kept | deleted |
docker compose down |
kept | kept, orphaned |
docker compose down -v |
deleted | deleted |
docker volume prune |
kept (since Engine 23) | deleted if unused |
Counting docker volume ls before and after confirms the rm -v and --rm rows: no volume is left behind in either case, while a plain docker rm leaves one. A Compose down followed by up doesn’t reattach the old anonymous volume either; it simply stays on disk. Orphaned anonymous volumes are the classic reason a CI host’s disk fills up; every test run of a Postgres image leaves one behind. The no space left on device guide covers cleaning those up safely.
The other trap with anonymous volumes is the one from the image/container question: you recreate a database container, a fresh anonymous volume gets attached, and the database looks empty. The old data is in some other hex-named volume. Name your volumes.
Compose: where down -v deletes your database
In Compose, top-level volumes: entries become named volumes prefixed with the project name:
name: shop
services:
app:
image: alpine:3.20
command: ["sh", "-c", "date >> /data/log; cat /data/log"]
volumes:
- appdata:/data
volumes:
appdata:$ docker compose up
Volume shop_appdata Created
...
$ docker compose down
Container shop-app-1 Removed
Network shop_default Removed
$ docker volume ls
DRIVER VOLUME NAME
local shop_appdata
$ docker compose down -v
Volume shop_appdata RemovedPlain down keeps the data, so the next up sees every line appended so far. down -v is the reset button. It’s useful in a local dev loop and catastrophic when someone pastes it from a README onto a shared staging box. If a volume must never be deleted by Compose, declare it external: true and create it outside the project; down -v never removes external volumes.
Renaming the project (a different directory name, or a changed name:) also changes the volume name. The old volume still exists, the stack just doesn’t look for it any more. “Compose lost my data after I moved the folder” is usually this.
The first-mount copy and why upgrades don’t change it
When you mount an empty volume onto a path where the image already has files, Docker copies the image’s files into the volume first. Mounting a fresh volume over Alpine’s /etc/apk:
$ docker run --rm -v seed:/etc/apk alpine:3.20 ls /etc/apk
arch
keys
protected_paths.d
repositories
worldWith --mount type=volume,src=nocopy,dst=/etc/apk,volume-nocopy the copy is skipped and the directory is empty. Bind mounts never do this copy; they always hide what’s underneath.
The copy happens only once, when the volume is empty. After that, the volume’s contents win. So if version 2 of your image ships new default config files in a path that’s backed by a volume, containers keep showing version 1’s files. This bites people who put application code or static assets in a volume: they deploy a new image and the old assets stay. Volumes are for state the application writes, not for things the image provides.
Backing up and moving a volume
There’s no docker volume export. The standard method is a throwaway container that mounts the volume read-only and a host directory for the archive:
docker run --rm \
-v data:/data:ro \
-v "$PWD/backup":/backup \
alpine:3.20 tar czf /backup/data.tgz -C /data .$ tar tzf backup/data.tgz
./
./state.txtRestore is the same in reverse (tar xzf into a new volume). For a live database, a filesystem copy taken while it’s writing can be inconsistent; use the database’s own dump tool (pg_dump, mysqldump) or stop the container first. Volume drivers other than local (NFS, cloud block storage) change where the bytes live but not these lifecycle rules.
Interview practice
Your team’s compose.yaml runs Postgres with a named volume pgdata. A developer reports that after pulling the latest changes and running docker compose up, the database is empty, but nobody ran down -v. The diff shows someone added name: payments at the top of the Compose file. What happened and how do you recover?
Answer
Compose prefixes volume names with the project name. Before the change the project name came from the directory, say backend, so the data lived in backend_pgdata. With name: payments the project is now payments, Compose looked for payments_pgdata, didn’t find it, and created a new empty volume. Nothing was deleted.
Confirm with docker volume ls: both backend_pgdata and payments_pgdata should be listed. To recover, either point the service at the old volume (declare pgdata with name: backend_pgdata and external: true), or copy the data across with a temporary container that mounts both volumes and runs cp -a. Stop the database first so the copy is consistent. Going forward, setting explicit name: values on important volumes makes them independent of the project name.
Keep going
- Docker interview questions
- Docker MCQ practice
- Docker bind mounts and host coupling
- Docker tmpfs mounts
- Official reference: Docker volumes