- A container’s writable layer is deleted along with the container, so important data needs a mount.
- Named volumes are managed by Docker, and are the usual choice for databases and production data.
- Bind mounts map a host folder, which makes them ideal for live code during development.
docker compose down -vdeletes volumes, so use it with care, and keep tested backups.
You start a database in a container, add some data, remove the container and start a new one. The data is gone. Almost everyone learns this lesson the hard way. Docker volumes and bind mounts exist to keep data alive beyond the life of any one container.
This guide explains how each one works and how to choose. The commands and files here are standard examples, based on Docker’s documented behaviour, and we did not run them for this article, so try them first on throwaway data. Details can differ between Docker versions and on Docker Desktop. If you are new to containers, begin with our Docker tutorial for beginners.
Why container data disappears
A Docker image is made of read-only layers. When a container starts, Docker adds a thin writable layer on top, and any changes go there. That layer survives a stop and restart, but it is removed when the container is deleted.
docker run -d --name db -e POSTGRES_PASSWORD=devonly postgres:17
# ... create tables and add data ...
docker rm -f db
docker run -d --name db -e POSTGRES_PASSWORD=devonly postgres:17
# The data you added is not there
This is by design, since it lets you replace containers freely. It does mean that anything you need to keep has to live outside the container. There are three ways to do that:
| Type | Stored in | Survives container removal? |
|---|---|---|
| Named volume | An area managed by Docker | Yes |
| Bind mount | A host path that you choose | Yes |
| tmpfs mount | Host memory | No |
Docker volumes vs bind mounts side by side
| Aspect | Named volume | Bind mount |
|---|---|---|
| Managed by | Docker | You |
| Location | Docker’s storage area | Any path on the host |
| Depends on host folders? | No | Yes |
| Editing from the host | Awkward by design | Easy, changes appear instantly |
| Empty target pre-filled from image? | Yes | No, the host path hides it |
| Docker CLI commands | Yes, docker volume |
No |
| Drivers for remote storage | Yes | No |
| Best for | Databases, production data | Source code in development |
Named volumes
A volume is storage created and managed by Docker. You refer to it by name, and you do not need to care where it physically lives.
docker volume create pgdata
docker run -d --name db \
-e POSTGRES_PASSWORD=devonly \
-v pgdata:/var/lib/postgresql/data \
postgres:17
Now the database files live in pgdata. Delete the container and start a new one with the same volume, and your data will still be there. On Linux, volumes usually sit under /var/lib/docker/volumes/, but you should not edit those files directly. On Docker Desktop, they live inside a virtual machine, not in a normal folder.
The path inside the container depends on the image, so check the image’s documentation. Mounting to the wrong path is a classic mistake: everything appears to work, but the data is still being written to the container layer. Newer major versions of an image may also change this path, so read the release notes before upgrading.
Docker will create a named volume automatically if it does not exist. An anonymous volume, created with no name, also works, but it gets a long random ID and is easy to lose track of. Always give your volumes names.
Bind mounts
A bind mount maps a specific host path directly into the container. Both sides see the same files, immediately. This makes it perfect for development, as edits in your editor take effect in the running container without a rebuild:
docker run -d --name web \
-p 8080:80 \
-v "$(pwd)/site:/usr/share/nginx/html:ro" \
nginx:1.27
Edit a file in site/, refresh the browser and you will see the change. The :ro makes the mount read-only inside the container, which is a good habit when the container has no need to write there.
Bind mounts come with some trade-offs:
- They tie you to the host. The path has to exist on each machine, so setups are less portable.
- They hide the image’s content. If you mount over
/app, whatever the image had in there becomes invisible. A common example isnode_modulesgoing missing. - They expose the host. A container with a writable mount can change or delete real host files. Never mount sensitive paths like
/or the Docker socket unless you fully understand the risk. - Performance can vary. On Docker Desktop for macOS and Windows, file sharing crosses a virtualization boundary, and can be slower than on Linux.
tmpfs mounts
A tmpfs mount lives in the host’s memory, and is never written to the container layer. It vanishes when the container stops, so it suits temporary files, caches and short-lived sensitive data:
docker run -d --tmpfs /app/cache:size=64m myapp:1.0
tmpfs is available on Linux hosts. It can also speed up databases in throwaway test environments, where you do not care about keeping the data.
-v or –mount?
Both flags work. --mount is longer, but it is more explicit, and many people prefer it in scripts:
# Short form
docker run -v pgdata:/var/lib/postgresql/data postgres:17
# Explicit form
docker run --mount type=volume,source=pgdata,target=/var/lib/postgresql/data postgres:17
# Bind mount, read-only
docker run --mount type=bind,source="$(pwd)"/site,target=/usr/share/nginx/html,readonly nginx:1.27
There is one meaningful difference. With -v, a missing host path gets created for you as an empty folder, which can hide a typo. With --mount and a bind type, Docker reports an error instead. Also, with -v, the left side decides the type: a name means a volume, and a path means a bind mount. So -v data:/x and -v ./data:/x do very different things.
Volumes in Docker Compose
Most real projects declare storage in a Compose file. This one uses both kinds, each for its proper job:
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- ./src:/app/src # bind mount: live code
depends_on:
- db
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: devonly
volumes:
- pgdata:/var/lib/postgresql/data # named volume: data
volumes:
pgdata:
Named volumes must be declared in the top-level volumes section. Compose prefixes the actual name with the project name. See our Docker Compose tutorial for a full walkthrough.
Tip: docker compose down keeps named volumes, but docker compose down -v deletes them, along with your data. Be especially careful about scripts and shell history that include -v.
Useful volume commands
| Command | What it does |
|---|---|
docker volume ls |
Lists volumes |
docker volume create name |
Creates a volume |
docker volume inspect name |
Shows details, including its location |
docker volume rm name |
Deletes a volume and its data |
docker volume prune |
Removes unused volumes |
docker system df -v |
Shows disk use, including volumes |
Check what prune will remove before you confirm it, since this cannot be undone. Volumes that no container currently uses may still hold important data.
Back up and restore a volume
A volume can be backed up by running a short-lived container that mounts both the volume and a host folder:
# Back up
docker run --rm \
-v pgdata:/data:ro \
-v "$(pwd)":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .
# Restore into a new volume
docker volume create pgdata_restored
docker run --rm \
-v pgdata_restored:/data \
-v "$(pwd)":/backup \
alpine tar xzf /backup/pgdata.tar.gz -C /data
For a live database, copying files like this can capture an inconsistent state. Stop the container first, or better, use the database’s own tool, such as pg_dump for PostgreSQL. And a backup you have never restored is really just a hope, so test the restore.
File permissions
“Permission denied” is the most common problem with mounts. Files keep their numeric user and group IDs on Linux, so if a container runs as user 1000 and the host folder belongs to another user, writes will fail. Some ways to fix this:
- Run the container with a matching user:
docker run --user "$(id -u):$(id -g)" ... - Set up correct ownership in your image, as described in our Dockerfile best practices.
- Prefer named volumes for data, as a new volume takes on the ownership of the image’s folder when it is first used.
- On systems using SELinux, bind mounts may need a
:zor:Zlabel.
Avoid the temptation of chmod 777, as our guide to Linux commands explains.
Which should you use?
| Situation | Use |
|---|---|
| Database data | Named volume |
| Live-editing source code | Bind mount |
| A configuration file from your repository | Read-only bind mount |
| Sharing data between containers | Named volume |
| Temporary cache or scratch files | tmpfs |
| Throwaway test database | tmpfs, or no mount |
| Application code in production | Build it into the image |
That last row matters. In production, code belongs in the image, so that what you tested is what you run. In clusters, data storage works differently again, through persistent volumes, which are covered in the wider Kubernetes documentation. Our Docker vs Kubernetes guide explains how the two relate.
Common mistakes
- Mounting to the wrong path. The data still lives in the container layer, and it disappears later on.
- Running
down -vcasually. This deletes your named volumes. - Using relative paths with
-v. Forms like-v data:/xcreate a volume rather than a folder mapping. - Putting secrets in writable bind mounts. Mount them read-only, or use a secrets feature.
- Pointing two database containers at one volume. This can corrupt the data.
- Having no backups. A volume is not a backup. It lives on the same disk.
Final thoughts
The rule of thumb is short: named volumes for data, bind mounts for code you are editing, and tmpfs for things that should not last. Try it on a throwaway database. Create a named volume, add a row, delete the container, start a new one, and confirm that your row survived. Then practise a backup and a restore before you need one.
Frequently asked questions
What is the difference between a Docker volume and a bind mount?
A volume is managed by Docker and stored in its own area. A bind mount maps a specific host path that you control, directly into the container.
Does stopping a container delete its data?
No. Data in the writable layer survives a stop and restart. It is lost when the container is removed, unless it is stored in a volume or bind mount.
Do Docker volumes persist after docker compose down?
Yes, named volumes remain after docker compose down. Adding the -v flag deletes them, along with any data they contain.
Where are Docker volumes stored?
On Linux, usually under /var/lib/docker/volumes. On Docker Desktop, they live inside a virtual machine. Use docker volume inspect to see the location.
Can multiple containers share a volume?
Yes, several containers can mount one volume. The application must handle concurrent access safely, and databases generally should not share data files this way.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.