A container’s filesystem is disposable. Remove the container and everything written inside it is gone — which is exactly what you want for the app binary, and exactly what you don’t want for a database. Volumes are how data outlives the container that wrote it.
Docker gives you two ways to do this, and people mix them up constantly. A bind mount maps a folder you choose on the host into the container. A named volume is storage Docker creates and manages for you, referenced by a name instead of a path. They look similar in a -v flag, but they behave differently and suit different jobs.
This guide lays out the real differences, when each one is the right tool, where named volumes actually live, the permission gotchas that trip people up, how to back them up, and the down -v mistake that has erased more than one production database.
The two mount types side by side
Start with the shapes, because the syntax is where the confusion begins. Both use -v host-side:container-side, but the host side is what differs:
# Bind mount — host side is a PATH you control
docker run -v /home/sam/app:/usr/src/app myimage
# Named volume — host side is a NAME Docker manages
docker run -v app-data:/usr/src/app myimage
That first character tells you which is which. A leading / or ./ means a path, so it’s a bind mount. A bare word like app-data means a named volume Docker looks after.
Bind mount vs named volume at a glance
| You specify | Bind mount: an exact host path. Named volume: just a name. |
|---|---|
| Managed by | Bind mount: you. Named volume: Docker. |
| Lives where | Bind mount: wherever you point it. Named volume: /var/lib/docker/volumes/. |
| Edit from host | Bind mount: yes, directly. Named volume: not meant to (go through a container). |
| Best for | Bind mount: source code, config files. Named volume: databases, app data. |
| Removed by down -v | Bind mount: no (it's your folder). Named volume: yes. |
| Portability | Bind mount: tied to host path. Named volume: portable, Docker-managed. |
When to use a bind mount
Reach for a bind mount when you want to see and edit the files yourself, from the host, right now.
The classic case is development. You bind-mount your source code into the container so that editing a file in your editor is instantly reflected inside the running app — no rebuild, no copy step:
services:
web:
build: .
volumes:
- ./src:/usr/src/app/src # live-edit code from the host
ports:
- "3000:3000"
The other strong use is configuration. Mounting a single config file into a container lets you manage it with your normal tools and version it in git:
services:
proxy:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # :ro = read-only
Note the :ro on the end — read-only. For config the container should never modify, that’s a good habit; it stops a misbehaving process from rewriting your file.
What bind mounts are bad at is portability. The path /home/sam/app exists on your machine and probably nowhere else. Hardcode that into a production stack and the setup breaks the moment you move it to a server with a different layout.
When to use a named volume
Use a named volume for data the application owns and must keep: databases, user uploads, anything generated at runtime that you don’t hand-edit.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: change-me
volumes:
- db-data:/var/lib/postgresql/data # named volume
volumes:
db-data:
The volumes: block at the bottom declares db-data so Docker manages it. The advantages over a bind mount here are concrete. Docker sets correct ownership when it creates the volume, so you dodge most permission headaches. There’s no host path baked in, so the same file works on your laptop and the server. And the data is decoupled from any container — destroy and recreate the Postgres container as often as you like, the volume stays.
Where named volumes actually live
A common question: if Docker manages the volume, where’s the data? On Linux, named volumes sit under Docker’s data directory:
/var/lib/docker/volumes/<volume-name>/_data
You can ask Docker for the exact location and details of any volume:
docker volume inspect db-data
The Mountpoint field in the output is the real path on disk. Useful commands for managing volumes:
# List all volumes
docker volume ls
# Inspect one (shows the mountpoint and driver)
docker volume inspect db-data
# Create one explicitly (Compose usually does this for you)
docker volume create db-data
# Remove a specific volume (must not be in use)
docker volume rm db-data
The permissions problem (and how to actually fix it)
Bind mounts are where “permission denied” shows up, and the reason is simple once you see it. A bind mount keeps the host’s ownership. Files owned by UID 1000 on the host arrive in the container still owned by UID 1000. If the process inside the container runs as a different user, it can’t read or write them.
The wrong fix is chmod -R 777 on the folder. It papers over the problem and opens the files to everyone. The right fix is to make the UIDs line up.
Either run the container as the user that owns the files:
docker run --user $(id -u):$(id -g) -v "$PWD":/app myimage
Or change the host folder’s ownership to match the UID the container expects (check the image’s docs for which user it runs as):
sudo chown -R 1000:1000 ./data
Named volumes mostly sidestep this. When Docker first creates a named volume for a container, it initializes the contents with the image’s existing ownership, so the process usually finds files it can write. It’s one of the quieter reasons named volumes are easier for databases.
Permission fixes — pick the right one
| Align the container user | --user $(id -u):$(id -g) — run as the host file owner. |
|---|---|
| Fix host ownership | chown the bind-mounted folder to the UID the image uses. |
| Switch to a named volume | For app data, let Docker handle ownership at creation time. |
| chmod 777 everything | Avoid. Hides the real issue and weakens file security. |
Backing up a named volume
Because named volumes don’t map to an obvious host folder, backing them up needs one extra step: mount the volume into a throwaway container alongside a host folder, then archive it across.
# Back up the 'db-data' volume to a tar.gz in the current folder
docker run --rm \
-v db-data:/data \
-v "$PWD":/backup \
alpine tar czf /backup/db-data.tar.gz -C /data .
That spins up a tiny Alpine container, mounts the volume read side at /data and your current directory at /backup, tars the contents, and removes itself (--rm). To restore into a fresh volume, reverse it:
docker run --rm \
-v db-data:/data \
-v "$PWD":/backup \
alpine sh -c "tar xzf /backup/db-data.tar.gz -C /data"
The down -v trap
This is the one that costs people data, so read it carefully. docker compose down and docker compose down -v look nearly identical and behave very differently.
Plain down stops and removes the containers and the default network. Named volumes stay. Run up -d again and your database is right where it was.
Add -v and Compose also deletes every named volume declared in the file. On a stack with a database, that erases the database. No recycle bin, no undo.
docker compose down # safe — keeps named volumes (your data)
docker compose down -v # DESTROYS named volumes declared in the file
Before you run down -v
- List the named volumes in the volumes: block of your compose file
- Check whether any hold a database, uploads, or anything you can't regenerate
- Back up the volume first if the data matters (tar method or a db dump)
- Confirm you want a clean slate, not just a stop or restart
- For a normal stop, use plain 'down' or 'stop' instead
Choosing between them
A short decision rule covers most cases. If you’ll edit the files yourself from the host — source code in development, a config file you version in git — use a bind mount. If the application owns the data and just needs it to persist — a database, user uploads, runtime state — use a named volume.
And there’s no rule against using both in one container. A web app might bind-mount its source for live editing while keeping its database in a named volume. They’re separate mounts on separate paths, and mixing them is normal.
For the bigger picture of how volumes fit into multi-container apps, see the Docker Compose beginner guide, and browse the Docker & Containers guides for more storage and networking topics.