How to Create and Use Docker Volumes
Table of Contents
A container’s own filesystem is ephemeral: docker rm deletes it, and every byte written inside it disappears. Volumes are how you keep data that needs to outlive the container — a database’s data directory, uploaded files, anything you can’t afford to lose on the next deploy.
Docker gives you three mechanisms, and picking the right one matters.
Named volumes — the default choice #
docker volume create pgdata
docker run -v pgdata:/var/lib/postgresql/data postgres
Docker creates and manages the storage location itself (under /var/lib/docker/volumes/ on Linux). You reference it by name, not by host path. This is the right default for anything you want to persist reliably: databases, message queue state, anything the application itself doesn’t need to know the physical location of.
docker volume ls # list all volumes
docker volume inspect pgdata # see where it actually lives on disk
docker volume rm pgdata # delete it (only if unused)
docker volume prune # remove all unused volumes
Named volumes survive docker rm on the container that used them — they’re only deleted when you explicitly remove the volume itself, or run a container with docker rm -v.
Bind mounts — for local development #
docker run -v $(pwd)/src:/app/src myapp
Maps a specific directory from the host directly into the container. Changes on either side are immediately visible on the other — which is exactly why this is the standard pattern for local development (edit code on the host in your normal editor, see it live inside the running container), and exactly why it’s a poor choice for production data (it ties the container to one specific host’s filesystem layout, and gives the container direct access to arbitrary host paths).
tmpfs mounts — for secrets and scratch space #
docker run --tmpfs /app/cache myapp
Stored in host memory only, never written to disk, and gone the moment the container stops. Use this for temporary scratch space, or for anything sensitive you specifically don’t want persisted anywhere — a decrypted secret a process needs at runtime, for instance.
The modern --mount syntax #
-v is shorthand; --mount is more explicit and generally preferred in scripts and Compose files because it’s harder to typo into the wrong behavior:
docker run --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres
docker run --mount type=bind,source=$(pwd)/src,target=/app/src myapp
Backing up a named volume #
Since Docker owns the storage location, you don’t just cp it directly — spin up a throwaway container that mounts the volume and archives it:
docker run --rm -v pgdata:/data -v $(pwd):/backup alpine \
tar czf /backup/pgdata-backup.tar.gz -C /data .
Restoring is the same idea in reverse: mount the (new) volume and untar into it.
Volume permissions #
A frequent surprise: a named volume’s ownership is set by whatever the container that first wrote to it was running as. If your image runs as a non-root USER (as it should — see
What Is a Dockerfile?) but the base image’s default process runs as root, you can end up with permission mismatches between containers sharing a volume. See
What Is the Best Way to Manage Permissions for Docker Shared Volumes? for the details.