Suggest an editImprove this articleRefine the answer for “What is a Docker volume and why do you need it?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A Docker volume** is Docker-managed persistent storage that lives outside any container's writable layer. Containers can come and go; the volume and its data survive. ```bash $ docker volume create pgdata $ docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16 $ docker rm -f db # data still on disk in pgdata $ docker run -d --name db2 -v pgdata:/var/lib/postgresql/data postgres:16 # new container, same data ``` **Key:** anything you write to the container's writable layer dies with the container. Anything written to a volume mount path persists. Use volumes for databases, uploads, logs, any state worth keeping.Shown above the full answer for quick recall.Answer (EN)Image**A Docker volume** is the standard way to keep data alive across container restarts and rebuilds. Containers are designed to be replaceable; volumes are the part that is *not* replaceable. ## Theory ### TL;DR - A volume is a **named, Docker-managed directory** outside any container's filesystem. - Container A writes to `/data` (mounted from volume `pgdata`); you `docker rm` A; create container B with the same volume mount; B sees A's data. - Volumes live under `/var/lib/docker/volumes/<name>/_data` on Linux. On Mac/Windows, behind the Docker Desktop VM. - Created on `docker volume create` or implicitly when first referenced by `docker run -v`. - The recommended way to handle persistent state. The alternative (writing to the container's writable layer) loses data on container removal. ### Quick example ```bash # Create the volume explicitly (or skip and let docker run create it) $ docker volume create pgdata pgdata # Run a database that stores its files in the volume $ docker run -d --name db \ -v pgdata:/var/lib/postgresql/data \ -e POSTGRES_PASSWORD=devpass \ postgres:16 # Insert some data $ docker exec -it db psql -U postgres -c "CREATE TABLE users(id int);" # Destroy the container - the volume and its data survive $ docker rm -f db # Start a fresh container; old data is still there $ docker run -d --name db2 \ -v pgdata:/var/lib/postgresql/data \ -e POSTGRES_PASSWORD=devpass \ postgres:16 $ docker exec db2 psql -U postgres -c "\\dt" # users table is still listed ``` Four `docker rm`s could not erase the data. That is what volumes are for. ### Why containers without volumes lose data A container's filesystem is image layers (read-only) plus a thin **writable layer** on top via OverlayFS. Writes to the writable layer go through copy-on-write into the upperdir. When you `docker rm`, the upperdir is deleted. Anything in it is gone. A volume mount intercepts writes to a specific path and routes them outside the union FS to a host directory that Docker manages independently. The container's writable layer never holds that data; `docker rm` cannot touch it. ### Volume lifecycle commands ```bash docker volume create <name> # explicit create with options docker volume ls # list all volumes docker volume inspect <name> # show driver, mountpoint, labels docker volume rm <name> # delete volume (must not be in use) docker volume prune # delete all unused volumes ``` Most workflows skip `volume create` because `docker run -v <name>:<path>` auto-creates the volume on first use. Explicit creation matters when you need driver-specific options (`--driver`, `--driver-opt`). ### Mount syntax: `-v` vs `--mount` ```bash # Short -v form (compact, common in tutorials) docker run -v pgdata:/var/lib/postgresql/data postgres:16 # Verbose --mount form (explicit, recommended for production) docker run --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres:16 ``` Both work. `--mount` is more verbose but harder to misread (no "is the first part a host path or a volume name?" ambiguity). Production scripts and Compose files generally prefer `--mount`. ### Common mistakes **Putting state in the writable layer** ```bash # WRONG: writes go to container's writable layer; lost on rm $ docker run -d --name db postgres:16 $ docker rm -f db # bye, tables # RIGHT: writes go to volume; survive container removal $ docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16 ``` The first form looks like it works (data is there while the container runs) and silently destroys data on the first `docker rm`. **Forgetting which path inside the container holds state** Every image documents its data path. Postgres uses `/var/lib/postgresql/data`. Mongo uses `/data/db`. Redis (with persistence) uses `/data`. Mount the wrong path and you mount over an empty directory while the real state still goes to the writable layer. Check the image's docs (or its `Dockerfile`'s `VOLUME` directive — that lists the paths the image expects to be volume-mounted). **Deleting a volume that is in use** ```bash $ docker volume rm pgdata Error response from daemon: remove pgdata: volume is in use ``` Stop and remove the containers first, then delete. Or `docker rm -v` to remove a container plus its anonymous volumes. **Mounting a non-empty container path on top of a volume on first run** When you mount a *named* volume into a container path that already has files (from the image), Docker copies those image files into the empty volume on the first start. After that, the volume is the source of truth; image updates do not propagate. Surprising the first time you upgrade a database image and wonder why the new files did not appear. ### Real-world usage - **Databases in production:** every Postgres, MySQL, Mongo, Redis container in production uses a named volume for its data dir. No exceptions. - **Local development with Docker Compose:** `volumes:` block at the top of `compose.yaml` defines named volumes; services mount them. Stop and start the stack as much as you want — data persists. - **CI/CD test fixtures:** test runners spin up a Postgres container with a named volume, run migrations + seed once, then re-use the volume across multiple test runs to skip setup time. - **Application uploads:** `/app/uploads` mounted to a named volume for an app that accepts user file uploads. Otherwise every redeploy wipes them. ### Follow-up questions **Q:** What happens if I forget the `-v` flag entirely? **A:** Anything the container writes goes into its writable layer. The container works fine while running. The moment you `docker rm`, all that state is gone. For ephemeral workloads (CI builds, tests) that is intentional. For databases or anything you care about, it is a data-loss bug. **Q:** Where exactly are volume files stored on my disk? **A:** On Linux: `/var/lib/docker/volumes/<volume-name>/_data/`. On macOS / Windows, inside the Docker Desktop Linux VM (not directly visible on the host filesystem). Find the path with `docker volume inspect <name> --format '{{.Mountpoint}}'`. **Q:** Can two containers share a volume? **A:** Yes. Mount the same volume into two containers and they both see the same files. Useful for sidecar log shippers, shared cache, or producer/consumer patterns. Concurrent writes can clobber each other; coordinate at the application layer. **Q:** What is an anonymous volume? **A:** A volume created without a name (auto-generated UUID). Happens when a Dockerfile has `VOLUME /path` with no explicit `-v` at run time. They are easy to lose track of — `docker volume ls -f dangling=true` finds them, `docker volume prune` deletes them. Prefer named volumes. **Q:** (Senior) When would you NOT use a Docker volume? **A:** Three cases. (1) For ephemeral state (CI builds, single-test-run containers) — skip the volume; the data is meant to die. (2) For development where you want host-side editing of the same files — use a bind mount instead of a volume. (3) For state that demands a specific filesystem (high-IOPS NVMe, distributed FS like NFS/Ceph) — use a volume *driver* plugin or a bind mount to that specific filesystem mount. ## Examples ### Postgres with a persistent volume ```bash $ docker volume create pgdata $ docker run -d --name pg \ -v pgdata:/var/lib/postgresql/data \ -e POSTGRES_PASSWORD=devpass \ -p 5432:5432 \ postgres:16 # Connect, do work $ psql -h localhost -U postgres postgres=# CREATE DATABASE app; # Container destroyed; volume retained $ docker rm -f pg # Recreate; data is still there $ docker run -d --name pg \ -v pgdata:/var/lib/postgresql/data \ -e POSTGRES_PASSWORD=devpass \ -p 5432:5432 \ postgres:16 $ psql -h localhost -U postgres -c '\l' | grep app app | postgres | UTF8 | ... ``` The `app` database survived a full container destruction. ### Compose example ```yaml # compose.yaml services: db: image: postgres:16 environment: POSTGRES_PASSWORD: devpass volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:7 volumes: - redis-data:/data volumes: pgdata: redis-data: ``` ```bash $ docker compose up -d # Postgres + Redis with persistent state. $ docker compose down # containers gone $ docker compose up -d # containers back; data unchanged $ docker compose down -v # -v also wipes volumes (destructive!) ``` `docker compose down` keeps volumes by default. The `-v` flag deletes them — that is the kill switch for state.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.