How to Use Docker Volumes and Bind Mounts With Examples
Learn when to use a Docker volume and when a bind mount: syntax with --mount, read-only mounts, what survives docker rm, and the permission trap.

A container's filesystem disappears with the container, so anything worth keeping has to live somewhere else. Docker offers two main places, and choosing the wrong one produces either lost data or a pile of permission errors. This post covers both with explicit syntax, as the groundwork for backing up volumes. The commands assume Docker Engine 27 on Debian 13.
Named volumes
docker volume create pgdata
docker run -d --name db --mount type=volume,src=pgdata,dst=/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=dev postgres:17
docker volume inspect pgdata --format '{{ .Mountpoint }}'/var/lib/docker/volumes/pgdata/_data
Docker owns the directory, creates it on first use and, if the volume is empty, copies the image's content at that path into it. Removing the container does not remove the volume, so docker rm -f db followed by another docker run with the same mount finds the data again. List and remove volumes with docker volume ls and docker volume rm. Avoid reaching into /var/lib/docker/volumes from the host; use a helper container instead.
Bind mounts
docker run --rm --mount type=bind,src="$PWD",dst=/app,readonly -w /app node:22 lsThe container sees the host directory at /app, and changes in either place show up in the other immediately. That is what a development workflow wants: edit on the host, run in the container. The absolute path matters, since a relative source is not allowed in --mount. Writes made as root in the container create root-owned files on the host, the root of most permission complaints; see fixing bind mount permissions.
-v versus --mount
docker run --rm -v /srv/missing:/data alpine ls /data
docker run --rm --mount type=bind,src=/srv/missing,dst=/data alpine ls /datadocker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /srv/missing
The short -v form creates the missing host directory, owned by root, and carries on. A typo in the path then looks like an empty project. --mount is longer and stricter, which is what you want in scripts. In Compose the long syntax under volumes: gives the same safety.
Choose between them
- Database files, uploads, anything the container owns: a named volume. It works on every platform and performs well.
- Source code, config files, anything you edit: a bind mount.
- Temporary scratch space:
--tmpfs /tmpkeeps it in memory and gone on exit. - Secrets: neither; use Docker secrets or a mounted file with tight permissions, as in env files and secrets.
Where this does not apply
On Docker Desktop for Mac and Windows, bind mounts cross a virtualization layer and can be much slower than volumes for file-heavy work such as node_modules; keep dependencies in a volume. Anonymous volumes, created by a VOLUME line in a Dockerfile without a name, accumulate silently, and docker volume prune cleans the unused ones, so check the list first. Network filesystems need driver options, not covered here. For the overall picture, see the Docker beginners tutorial.
Where the data really lives
A named volume is a directory that Docker creates and manages under its own data root, usually /var/lib/docker/volumes/ on Linux. You refer to it by name, and the daemon decides where on disk it sits. A bind mount maps a path you chose on the host into the container, and Docker does no management of it at all. That difference drives every other one. A volume's contents are created from the image's files the first time it is mounted empty, so a container started with an empty named volume over a directory that held files in the image sees those files copied in. A bind mount over the same directory hides the image's files completely, and the container sees only what is on the host, including nothing at all if the host directory is empty. This is why a bind mount of an empty folder over an application directory makes the application vanish.
On Docker Desktop for Mac and Windows, a container runs inside a virtual machine, so a bind mount crosses a file sharing layer between the host and the VM. It works, and it is slower than a volume for workloads with many small files, such as a package install or a database. A named volume lives inside the VM's disk and avoids that cost.
Ownership, permissions and the user in the container
A volume created by Docker is owned by root unless the image sets ownership on that path first, and the first-use copy preserves the owner of the files in the image. That is why a non-root application user can write to a volume only when the image prepared the directory with the right owner before declaring the mount point. A bind mount keeps the numeric owner of the host path, and the container sees numbers, not names. A file owned by user 1000 on the host is owned by user 1000 in the container, whether or not such a user exists there. Mismatched numbers are behind most permission errors, and the dedicated fix is a matter of aligning the user the process runs as with the owner of the directory.
Read-only mounts reduce risk. Add :ro or readonly to a bind mount of configuration or source that the container should never change. A bug or a compromised process then cannot modify the host files.
Lifecycle, cleanup and surprises
A named volume outlives the container that used it, and docker rm leaves it behind. That is the feature, and it is also how disks fill with orphaned data. docker volume ls lists them, docker volume inspect shows the mount point and labels, and docker volume prune removes the ones no container references. An anonymous volume, created by a VOLUME instruction or a mount with no name, gets a random hash name and is easy to lose track of. Prefer names. With Compose, docker compose down keeps volumes and -v removes them, so adding that flag in a habit deletes a database. Treat the flag as a deliberate act.
Choose by purpose. Use a volume for data the container owns, such as a database, because it is managed, portable between hosts through backup, and fast on every platform. Use a bind mount for files you edit on the host, such as source code in development or a configuration file you want to control directly. For production, volumes or an external storage driver are the norm, and bind mounts of host paths tie a service to one machine's layout.
Mistakes that cost an afternoon
Three mistakes recur. The first is mounting a bind to a path in the image that holds installed files, such as the application directory or a dependencies folder, which hides them and produces "module not found" errors that look unrelated to storage. Mount only the part you need, or mount source over a directory that does not hold installed packages. The second is relying on a volume to refresh when the image changes. A named volume copies image content only when it is empty, so a new image version does not update files inside an existing volume, and the container keeps running old assets. The third is using a relative host path in a command line, which is read as a volume name and not a path, since a name without a slash is a volume. Start a bind path with ./ or an absolute path so there is no doubt.
On Linux with SELinux, a bind mount needs the :z or :Z suffix to be readable, and on any system a typo in the host path makes Docker create an empty directory owned by root, which hides the mistake. The --mount syntax fails with an error for a missing host path, and that is one reason to prefer it in scripts.
See the Docker volumes documentation and bind mounts.


