$docker.recipes
·13 min read·Updated December 2025

The Complete Guide to Docker Volumes and Data Persistence

Everything you need to know about Docker volumes, bind mounts, and tmpfs — with practical patterns for backing up, migrating, and managing persistent data.

dockervolumesdata-persistencestorage

01Why Data Persistence Trips Up Docker Beginners

Docker containers are ephemeral by design. When a container is removed, everything inside it is gone. This is a feature, not a bug — it ensures clean, reproducible deployments. But it means that if you don't explicitly configure data persistence, you will lose data. I've seen this happen in production more times than I'd like to admit. A developer runs docker compose down and their database is gone. Someone rebuilds a container and their uploads disappear. These are completely preventable with a basic understanding of Docker's storage options. There are three ways to persist data in Docker: volumes (managed by Docker), bind mounts (mapped to host paths), and tmpfs (in-memory storage). Each has different use cases, and choosing the right one matters for performance, portability, and backup strategy.

02Named Volumes: The Default Choice

Named volumes are created and managed by Docker. They're stored in /var/lib/docker/volumes/ on the host, and they persist across container restarts, rebuilds, and upgrades. Named volumes are the right choice for: database data, application uploads, configuration databases, and any data that should survive container lifecycle events.
[docker-compose.yml]
1services:
2 db:
3 image: postgres:16-alpine
4 volumes:
5 # Named volume - managed by Docker
6 - pgdata:/var/lib/postgresql/data
7
8 nextcloud:
9 image: nextcloud:29
10 volumes:
11 # Named volume for application data
12 - nextcloud_data:/var/www/html
13
14# Declare named volumes at the top level
15volumes:
16 pgdata: # Docker manages the storage location
17 nextcloud_data:

Named volumes survive docker compose down but are removed by docker compose down -v (the -v flag). Never use -v unless you intentionally want to delete all data.

03Bind Mounts: When You Need Host Access

Bind mounts map a specific host directory into the container. Unlike named volumes, you control exactly where the data lives on the host filesystem. Bind mounts are the right choice for: configuration files you want to edit directly, development source code (for hot-reloading), log files you want to access from the host, and shared files between the host and container.
[docker-compose.yml]
1services:
2 traefik:
3 image: traefik:v3.1
4 volumes:
5 # Bind mount - specific host path
6 - ./traefik.yml:/etc/traefik/traefik.yml:ro
7 - ./acme.json:/acme.json
8 - /var/run/docker.sock:/var/run/docker.sock:ro
9
10 app:
11 image: node:22-alpine
12 volumes:
13 # Development: mount source code for hot-reloading
14 - ./src:/app/src
15 # But use named volume for node_modules (performance)
16 - node_modules:/app/node_modules
17
18volumes:
19 node_modules:

Use :ro (read-only) for bind mounts that the container should only read, like configuration files and the Docker socket. This limits the damage if the container is compromised.

04Backing Up and Migrating Volumes

Named volumes can be backed up by running a temporary container that mounts both the volume and a host directory:
[terminal]
1# Backup a named volume to a tar file
2docker run --rm \
3 -v pgdata:/source:ro \
4 -v $(pwd):/backup \
5 alpine tar czf /backup/pgdata-backup.tar.gz -C /source .
6
7# Restore from backup to a new volume
8docker run --rm \
9 -v new_pgdata:/target \
10 -v $(pwd):/backup \
11 alpine tar xzf /backup/pgdata-backup.tar.gz -C /target
12
13# Migrate volume between hosts:
14# 1. Backup on source host (above)
15# 2. Copy tar to destination: scp pgdata-backup.tar.gz user@newhost:
16# 3. Restore on destination host (above)
17# 4. Update docker-compose.yml to reference new volume name

05Common Patterns and Best Practices

After managing Docker volumes across dozens of projects, here are the patterns that work best: Separate data from config: Use named volumes for data (databases, uploads) and bind mounts for configuration files. This lets you version-control configs in Git while keeping data managed by Docker. One volume per concern: Don't share a single volume between unrelated services. If Nextcloud and Gitea both need storage, give them separate volumes. This simplifies backups and prevents permission conflicts. Use volume drivers for special cases: Docker supports volume plugins for NFS, cloud storage, encrypted volumes, and more. The local driver with specific mount options lets you use NFS or specific filesystem types. Never use anonymous volumes in production: Always name your volumes. Anonymous volumes (declared only in Dockerfile VOLUME instructions) are hard to identify and easy to accidentally delete. Check out our storage category for Docker Compose configurations of storage solutions like MinIO, Syncthing, and NFS that integrate with Docker volumes.

About the Author

Frank Pegasus

DevOps engineer and self-hosting enthusiast with over a decade of experience running containerized workloads in production. Creator of docker.recipes.