Key idea
Both put storage from outside the container at a path inside it, so files written there outlive the container. A volume is storage Docker creates and manages by name. A bind mount is a folder on your own machine. Use volumes for data, and bind mounts for files you edit while you develop.
Volumes
docker volume create notes-data
docker run --rm -v notes-data:/data alpine:3.22 sh -c 'echo saved >> /data/notes.txt'
-v notes-data:/data mounts the volume notes-data at /data inside the container. The file lands on the volume, not in the writable layer, so the next container that mounts notes-data sees it. If the volume doesn't exist yet, docker run creates it.
Docker keeps volumes in its own storage area, and you manage them by name with docker volume ls, inspect and rm. Removing a container never removes a named volume: you delete one on purpose.
docker volume inspect notes-data shows where the files live, such as /var/lib/docker/volumes/notes-data/_data. On Docker Desktop, that path is inside Docker's own small Linux machine, not a folder you can open on your Mac or PC. You reach a volume's files through a container.
Bind mounts
mkdir notes-folder
docker run --rm -v "$PWD/notes-folder:/data" alpine:3.22 sh -c 'echo saved >> /data/notes.txt'
When the left side is a path on your machine ($PWD makes it absolute), Docker mounts that folder instead. The file appears in notes-folder at once, and edits from either side show up on the other.
That makes bind mounts the tool for development: mount your source code, and a server that reloads on change picks up your edits without a rebuild. It also ties the container to one machine's folder layout, which is why bind mounts don't suit production data.
Choosing
- Data the app must keep, such as a database's files or uploads: a volume. Docker manages it, and it works the same on any machine.
- Files you edit while developing, such as source code or a config file: a bind mount.
- Scratch files nobody needs later: neither. The writable layer is fine.
Two things to know about both
- Mount a folder your app only writes to, such as
/data, never your code folder. A bind mount hides what the image had at that path. A new, empty volume gets a copy of it instead, and keeps that old copy after you ship a new image. - Compose declares them too (lesson 3.6.1).
docker compose downkeeps a stack's volumes;docker compose down -vdeletes them.
If a non-root app can't write to its volume
A new volume's folder belongs to root, so an app running as a non-root user (lesson 3.4.3) gets Permission denied writing to it. The fix goes in the Dockerfile: create the folder and give it to your user.
RUN mkdir /data && chown 10001:10001 /data
USER 10001
When Docker mounts a new, empty volume on a folder the image already has, it copies that folder's owner, so your user can write. Bind mounts don't do this: the folder keeps the owner it has on your machine.
Check yourself