Named Volumes
Measured on Docker Desktop 4.81.0 / Engine 29.6.1 (macOS, linux/arm64), 2026-09-30.
What is a named volume?
Section titled “What is a named volume?”A named volume is a storage unit that Docker creates and manages on your behalf. Docker stores volume data under its own directory (/var/lib/docker/volumes/ by default, on the machine that runs the engine). You refer to the volume by name — never by a host path.
Because Docker owns the storage location, named volumes:
- Work the same way on every OS the engine runs on (you pass a name, not a path).
- Survive
docker rm— the volume is separate from the container. - Can be shared between multiple containers simultaneously.
- Are the recommended way to persist application data (Docker docs: volumes).
Managing volumes
Section titled “Managing volumes”Create, list, inspect
Section titled “Create, list, inspect”docker volume create mydatadocker volume lsdocker volume inspect mydataReal output (the course’s volume is named docker-b2-mydata):
$ docker volume lsDRIVER VOLUME NAMElocal docker-b2-mydata
$ docker volume inspect docker-b2-mydata[ { "CreatedAt": "2026-09-30T16:29:16Z", "Driver": "local", "Labels": { "course-agent": "docker-b2" }, "Mountpoint": "/var/lib/docker/volumes/docker-b2-mydata/_data", "Name": "docker-b2-mydata", "Options": null, "Scope": "local" }]Remove a volume
Section titled “Remove a volume”$ docker volume rm docker-b2-nonexistError response from daemon: get docker-b2-nonexist: no such volume$ docker volume rm docker-b2-busy # a running container mounts itError response from daemon: remove docker-b2-busy: volume is in use - [b3b9cd6c...]A volume cannot be removed while any container (even a stopped one) references it. Remove the container first.
Mounting a volume into a container
Section titled “Mounting a volume into a container”There are two syntaxes. Both create the same mount.
Short -v syntax:
docker run -d --name myapp -v mydata:/app/data myimageLong --mount syntax (explicit and recommended for scripts):
docker run -d --name myapp \ --mount type=volume,source=mydata,target=/app/data \ myimageIf the volume mydata does not exist yet, Docker creates it automatically when you run the container.
Copy-up: an empty volume takes the image’s files
Section titled “Copy-up: an empty volume takes the image’s files”When you mount an empty volume on a directory that already has content in the image, Docker copies that content into the volume first. Bind mounts do not do this.
$ docker volume create docker-b2-html$ docker run --rm -v docker-b2-html:/usr/share/nginx/html nginx:1.31-alpine ls -l /usr/share/nginx/html-rw-r--r-- 1 root root 497 Sep 15 14:18 50x.html-rw-r--r-- 1 root root 896 Sep 15 14:18 index.html$ docker run --rm -v docker-b2-html:/data alpine:3.24 ls /data # a different image, same volume50x.htmlindex.htmlThe volume now holds nginx’s default pages even though the second container’s image has none. If the volume is not empty, its content wins and the image’s files at that path are hidden.
Anonymous volumes
Section titled “Anonymous volumes”A -v /data (no name) or an image VOLUME instruction creates an anonymous volume with a random 64-character name and the label com.docker.volume.anonymous:
$ docker run -d --name anon -v /data alpine:3.24 sleep 60$ docker inspect -f '{{range .Mounts}}{{.Type}} name={{.Name}} dst={{.Destination}}{{end}}' anonvolume name=886e1acf723f1af30efc071d3c179610f9f7eb902330b289d760d4dbcb9eb3f2 dst=/dataAnonymous volumes are easy to lose track of, which is why an image such as postgres (it declares VOLUME) leaves one behind every time you docker run it without naming a mount.
What docker rm -v and docker volume prune really delete
Section titled “What docker rm -v and docker volume prune really delete”Both commands are narrower than most people expect:
# rm -v removes anonymous volumes only: the named volume survives$ docker rm -f -v named # container mounted docker-b2-mydata:/data$ docker volume ls --filter label=course-agent=docker-b2 -qdocker-b2-htmldocker-b2-mydata
# prune without -a removes only unreferenced ANONYMOUS volumes$ docker volume prune -f --filter label=course-agent=docker-b2Deleted Volumes:405eac1fcb1292d90e253b04262d4fb3a18957f074ecae35d5c8600bb9607de7 # the anonymous one$ docker volume ls --filter label=course-agent=docker-b2DRIVER VOLUME NAMElocal docker-b2-htmllocal docker-b2-mydata
# -a also removes unreferenced NAMED volumes$ docker volume prune -f -a --filter label=course-agent=docker-b2Deleted Volumes:docker-b2-mydatadocker-b2-htmldocker rm -vdeletes the container’s anonymous volumes; a named volume stays (proven: bothdocker-b2-*volumes still listed).docker volume prune(Engine 29) deletes unused anonymous volumes; add-a/--allfor unused named volumes too (reference).- The
--filter label=…limited every prune above to this lesson’s objects. The Docker Desktop VM in this test held 77 volumes from other projects; an unfiltered-awould have deleted every unreferenced one of them. Treat-aas destructive.
Data persists across container replacements
Section titled “Data persists across container replacements”The key property of a named volume: you can delete the container and recreate it, and all the data written to the volume is still there.
# Run a container and write datadocker run --name writer -v mydata:/app/data alpine:3.24 sh -c "echo 'persistent' > /app/data/file.txt"
# Remove the containerdocker rm writer
# Start a brand-new container mounting the same volumedocker run --rm -v mydata:/app/data alpine:3.24 cat /app/data/file.txt# Output: persistentHands-on practice
Section titled “Hands-on practice”The snippet below runs a container, writes data to a named volume, removes the container, and mounts the same volume in a new container to prove the data survived.
# 1. Create a named volume
docker volume create demo-vol
# 2. Write data into the volume via a container
docker run --rm -v demo-vol:/data alpine:3.24 sh -c "echo 'data survives!' > /data/message.txt"
# 3. Read it back from a completely new container
docker run --rm -v demo-vol:/data alpine:3.24 cat /data/message.txt
# 4. Inspect the volume
docker volume inspect demo-vol
# 5. Clean up
docker volume rm demo-volReading AI-generated code
Section titled “Reading AI-generated code”An AI-written Compose file for a Postgres 18 database, copied from an older tutorial:
🐛 Spot the bug
services:db: image: postgres:18-alpine environment: POSTGRES_PASSWORD: <your-db-password> volumes: - pgdata:/var/lib/postgresql/datavolumes:pgdata:Show the answer
The container never becomes healthy. It exits at once with code 1 and a long explanation:
$ docker ps -a --filter name=docker-b2-pg --format '{{.Status}}'Exited (1) 4 seconds ago$ docker logs docker-b2-pgError: in 18+, these Docker images are configured to store database data in a format which is compatible with "pg_ctlcluster" (specifically, using major-version-specific directory names). ... Counter to that, there appears to be PostgreSQL data in: /var/lib/postgresql/data (unused mount/volume) ... The suggested container configuration for 18+ is to place a single mount at /var/lib/postgresql which will then place PostgreSQL data in a subdirectory ...The cause is in the image: docker image inspect postgres:18-alpine shows Volumes: {"/var/lib/postgresql":{}} and PGDATA=/var/lib/postgresql/18/docker. Up to Postgres 17 the data directory was /var/lib/postgresql/data; from 18 the image declares the parent as the volume. The old mount point puts the named volume where the image no longer looks, and Docker also attaches a fresh anonymous volume at /var/lib/postgresql (docker inspect listed both mounts). Mount the volume at /var/lib/postgresql instead:
services: db: image: postgres:18-alpine environment: POSTGRES_PASSWORD: <your-db-password> volumes: - pgdata:/var/lib/postgresqlvolumes: pgdata:With this file docker compose up -d --wait reported Healthy, show data_directory returned /var/lib/postgresql/18/docker, and the volume contained the directory 18/docker. Always read the image’s documented mount point (hub.docker.com/_/postgres) and check docker image inspect --format '{{json .Config.Volumes}}' when you upgrade a major version.
Mental Model
Section titled “Mental Model”A named volume is storage with a name and its own lifetime. The container references it; the reference is what keeps volume rm from working and what prune looks at. Mounting decides where in the container the storage appears, so the path must match what the program actually writes to — an image that moves its data directory (Postgres 18) silently changes which mount matters.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Named volume | Docker manages the path, portable across hosts, easy to back up with a throwaway container, copy-up seeds it | Cannot access the data directly from the host filesystem (on Desktop it is inside the VM) — always goes through a container |
| Anonymous volume | Zero config, VOLUME in an image gives you persistence by default | Random name, easy to orphan; rm -v/prune delete it, so “persistent” data may vanish |
| Bind mount | See and edit files directly from the host right away | Tied to a specific host path, hard to move between environments |
Common mistakes
Section titled “Common mistakes”- Using a bind mount instead of a named volume for production database data, losing portability.
- Running
docker compose down -vby accident and losing all the data in the volume. - Never planning a backup for a volume, assuming “a volume lasts forever” and never backing it up.
- Running
docker volume prune -aordocker system prune -a --volumeson a shared host without checking what else uses it. - Mounting the database volume at the path an older major version used.
Common Misconceptions
Section titled “Common Misconceptions”- “
docker rm -vdeletes the container’s volume.” Only anonymous volumes; named volumes always survive it. - “
docker volume prunedeletes all unused volumes.” Only unused anonymous volumes on Engine 29; unused named volumes need-a. - “The Mountpoint is a folder on my Mac.” On Docker Desktop it is inside the VM.
- “Mounting a volume always hides the image’s files.” Only if the volume is non-empty; an empty named volume is first populated from the image (copy-up).