Docker Volumes

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

How Docker persists data. Named volumes vs bind mounts vs tmpfs, why container data disappears, permissions problems, backups, and reclaiming disk space.

Anything a container writes to its own filesystem is destroyed when the container is removed. Volumes are how data outlives the container that created it.

This catches essentially everyone once. A database container is recreated, and the data is gone.

Why container data disappears

A container has a thin writable layer on top of the read-only image layers. It exists for the life of that container and is deleted with it.

docker run -d --name db postgres:17
# ... write some data ...
docker rm -f db
docker run -d --name db postgres:17      # empty. The data was in the old writable layer.

Note that docker stop and docker start preserve the writable layer, only docker rm destroys it. But since replacing containers is the normal way to deploy a new image, anything valuable must live outside.

The three mount types

TypeManaged byBest for
Named volumeDockerPersistent application data
Bind mountYou, via a host pathDevelopment, mounting host config
tmpfsMemory onlySecrets and scratch that must never touch disk
# Named volume
docker run -d -v pgdata:/var/lib/postgresql/data postgres:17

# Bind mount
docker run -d -v $(pwd)/src:/app/src myapp:dev

# tmpfs
docker run -d --tmpfs /tmp:size=64m myapp

The modern --mount syntax is more explicit and worth preferring in scripts, since -v silently creates a directory when a host path does not exist rather than erroring:

docker run -d --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres:17
docker run -d --mount type=bind,source="$(pwd)"/src,target=/app/src,readonly myapp:dev

Named volumes

Docker manages the location, the lifecycle and the permissions:

docker volume create pgdata
docker volume ls
docker volume inspect pgdata

They live under /var/lib/docker/volumes/ on Linux and inside the Docker Desktop VM on macOS and Windows, which is deliberate, because not caring where they live is the point.

To look inside one without a running container:

docker run --rm -v pgdata:/data alpine ls -la /data

Use named volumes for application data. They are portable between hosts, behave identically across platforms, and perform far better than bind mounts on macOS and Windows.

Bind mounts

A direct mapping of a host path into the container:

docker run -d -v $(pwd):/app -v /app/node_modules myapp:dev

The standard development pattern: mount your source so edits appear instantly, and use that second anonymous volume to shadow node_modules. Otherwise the host's empty or platform-mismatched directory hides the one installed during the build.

Two things to know.

They are slow on macOS and Windows. Every file access crosses into the Docker VM, which is painful for projects with many small files. Mount only what you need, keep dependency directories in named volumes, and enable VirtioFS in Docker Desktop.

Mount read-only where you can:

docker run -v /host/config:/etc/app:ro myapp

The permissions problem

On Linux, a container running as root writes files owned by root onto your host:

$ ls -l output/
-rw-r--r-- 1 root root  1024 Sep 16 10:00 report.csv

The container preserves real UIDs rather than remapping them. Fix it by running as your own user:

docker run --user $(id -u):$(id -g) -v $(pwd):/app myapp

The reverse also happens: a container running as a non-root user cannot write to a host directory owned by you, producing permission denied. Matching the UID is the reliable fix.

This is Linux-specific. Docker Desktop on macOS and Windows handles the mapping.

For named volumes Docker sets ownership from the image's declared user on first use, which is why they largely avoid this class of problem.

Backing up

There is no built-in backup command. The pattern is a throwaway container mounting both the volume and a host directory:

# Back up
docker run --rm -v pgdata:/data -v $(pwd):/backup alpine \
  tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .

# Restore
docker run --rm -v pgdata:/data -v $(pwd):/backup alpine \
  tar xzf /backup/pgdata-2026-09-16.tar.gz -C /data

For a database, prefer the engine's own dump tool. Copying live database files can capture a torn, inconsistent state:

docker exec db pg_dump -U postgres app > backup.sql

Reclaiming space

Volumes are excluded from docker system prune by default, deliberately, because they hold the data you least want to lose:

docker system df -v          # usage per volume
docker volume prune          # remove volumes no container references
docker system prune --volumes   # include them in a general clean

The hidden consumer is anonymous volumes. An image declaring VOLUME /data creates a fresh unnamed volume on every docker run if you do not mount one there, so repeatedly starting a database image leaves a trail of orphaned volumes holding real data.

docker volume ls -f dangling=true

Avoid them by always mounting a named volume explicitly, and by using --rm, which removes anonymous volumes on exit.

Explore further in this section

Sub-topicWhat you will learn
Named VolumesCreating, sharing and backing them up
Bind MountsDevelopment workflows and permissions
Volume ManagementInspecting, pruning and migrating

Quick reference

TaskCommand
Createdocker volume create <name>
Listdocker volume ls
Inspectdocker volume inspect <name>
Mount a named volumedocker run -v <name>:/path <image>
Bind mountdocker run -v $(pwd):/app <image>
Read-onlydocker run -v /host:/path:ro <image>
Look insidedocker run --rm -v <name>:/data alpine ls /data
Which container uses itdocker ps -a --filter volume=<name>
Usage per volumedocker system df -v
Remove unuseddocker volume prune

Key takeaways

  • The container's writable layer dies with the container. Anything valuable needs a volume.
  • docker stop preserves it; docker rm destroys it.
  • Named volumes for application data, bind mounts for development, tmpfs for secrets.
  • Bind mounts are slow on macOS and Windows because every access crosses into the Docker VM.
  • On Linux, container-written files are root-owned on the host. --user $(id -u):$(id -g) fixes it.
  • Shadow node_modules with an anonymous volume when bind-mounting a project directory.
  • docker system prune skips volumes unless you pass --volumes.
  • Anonymous volumes accumulate silently from images with a VOLUME instruction.

Check your understanding

Exercise

A database that keeps forgetting

A Postgres container is recreated after an image update and all data is gone. It was started with docker run -d postgres:17 and no volume. What happened?

Exercise

Pruning that left the volumes

A server is short on disk. docker system prune -a frees some space but docker volume ls still shows dozens of volumes. Why were they not removed?


Frequently Asked Questions

Why does my Docker container lose data when it restarts?

It does not lose data on restart, docker stop and docker start preserve the container's writable layer. Data is lost when the container is removed, which is what happens when you deploy a new image or run docker rm. Since replacing containers is the normal deployment model, anything valuable must live in a volume mounted into the container rather than in its own filesystem.

What is the difference between a volume and a bind mount?

A named volume is managed by Docker, referenced by name, and stored somewhere Docker chooses. A bind mount maps a specific host directory into the container. Use named volumes for persistent application data. They are portable across hosts, handle permissions better, and are considerably faster on macOS and Windows. Use bind mounts for development, where you want host edits reflected in the container immediately, and for mounting host configuration read-only.

How do I fix "permission denied" on a Docker volume?

On Linux, the container preserves real user IDs rather than remapping them, so a container running as a non-root user cannot write to a host directory owned by you, and a root container creates root-owned files on your host. Run the container as your own user with docker run --user $(id -u):$(id -g), or set a matching USER in the Dockerfile. Named volumes largely avoid this because Docker sets ownership from the image's declared user on first use.

How do I back up a Docker volume?

Use a throwaway container that mounts both the volume and a host directory, then archive one into the other: docker run --rm -v myvol:/data -v $(pwd):/backup alpine tar czf /backup/vol.tar.gz -C /data .. There is no built-in backup command. For databases, use the engine's own dump tool instead, pg_dump, mysqldump, because copying live database files can capture an inconsistent state that will not restore cleanly.

Why didn't docker system prune free up my volume space?

Because volumes are excluded by default, deliberately, since they usually contain the data you would least want removed by a routine cleanup. Use docker volume prune to remove volumes no container references, or docker system prune --volumes to include them. Check docker system df -v first to see which volumes are actually consuming space, and be aware the deletion is permanent.

What are anonymous volumes and why do I keep getting them?

When an image declares VOLUME /data in its Dockerfile and you run it without mounting anything at that path, Docker creates an unnamed volume with a random hexadecimal ID. Every docker run creates another, so repeatedly starting a database image leaves a trail of orphaned volumes each holding real data. Always mount a named volume explicitly at the documented path, and add --rm to one-off runs, which removes anonymous volumes when the container exits.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Docker Playground

Run the commands in a simulated terminal, no install needed.

When it breaks

Related Resources