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-alpine4 volumes: 5 # Named volume - managed by Docker6 - pgdata:/var/lib/postgresql/data78 nextcloud: 9 image: nextcloud:2910 volumes: 11 # Named volume for application data12 - nextcloud_data:/var/www/html1314# Declare named volumes at the top level15volumes: 16 pgdata: # Docker manages the storage location17 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.14 volumes: 5 # Bind mount - specific host path6 - ./traefik.yml:/etc/traefik/traefik.yml:ro7 - ./acme.json:/acme.json8 - /var/run/docker.sock:/var/run/docker.sock:ro910 app: 11 image: node:22-alpine12 volumes: 13 # Development: mount source code for hot-reloading14 - ./src:/app/src15 # But use named volume for node_modules (performance)16 - node_modules:/app/node_modules1718volumes: 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 file2docker run --rm \3 -v pgdata:/source:ro \4 -v $(pwd):/backup \5 alpine tar czf /backup/pgdata-backup.tar.gz -C /source .67# Restore from backup to a new volume8docker run --rm \9 -v new_pgdata:/target \10 -v $(pwd):/backup \11 alpine tar xzf /backup/pgdata-backup.tar.gz -C /target1213# 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 name05Common 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.