Skip to main content

What is a Docker volume and why do you need it?

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 rms 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.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Comments

No comments yet