Understanding Docker Volumes
Docker containers are ephemeral by design. When a container is stopped and removed, all data written inside the container filesystem is lost. This is a feature, not a bug — it ensures containers are reproducible and stateless. But most real-world applications need persistent data: databases, uploaded files, configuration, logs.
Docker provides three mechanisms for data persistence:
- Named Volumes: Managed by Docker, stored in
/var/lib/docker/volumes/. The recommended approach for most use cases. - Bind Mounts: Map a specific host directory into the container. Useful for development but requires careful permission management.
- tmpfs Mounts: In-memory storage that is discarded when the container stops. Used for sensitive data that should never persist to disk.
This guide focuses on troubleshooting the most common volume-related problems: permission errors, data loss, orphaned volumes, and bind mount pitfalls.
Prerequisites
- Docker Engine 20.10+ installed on Linux, macOS, or Windows with WSL2.
- Shell access to the Docker host.
- Basic familiarity with Docker CLI commands (
docker run,docker inspect,docker volume).
Common Docker Volume Problems
1. Data Loss on Container Restart
Symptom: Your database or application data disappears every time you recreate the container.
Root Cause: No volume is mounted. Data is written to the container’s writable layer which is removed with the container.
Solution: Always mount a named volume for persistent data:
# Create a named volume
docker volume create myapp-data
# Run the container with the volume mounted
docker run -d \
--name postgres-db \
-v myapp-data:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:16
For Docker Compose, declare volumes explicitly:
services:
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: secret
volumes:
db-data:
driver: local
Critical: If you use
docker-compose down -v, the-vflag will delete all volumes defined in the compose file. Usedocker-compose down(without-v) to keep your data.
2. Permission Denied Errors
Symptom: Container logs show Permission denied when trying to read or write files in a mounted volume.
Root Cause: The process inside the container runs as a specific UID (e.g., 999 for PostgreSQL, 1000 for Node.js) that does not match the ownership of the host directory.
Diagnostic steps:
# Check what user the container process runs as
docker exec mycontainer id
# Check ownership of the mounted path on the host
ls -lan /path/to/host/directory
# Check the mount point inside the container
docker exec mycontainer ls -lan /data
Solutions:
A) Use a named volume (Docker manages permissions automatically):
docker run -d -v myvolume:/data myimage
B) Match the UID with --user:
# Run as the current host user
docker run -d --user "$(id -u):$(id -g)" -v ./data:/data myimage
C) Fix ownership in the entrypoint:
# In your Dockerfile
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
# entrypoint.sh
#!/bin/sh
chown -R appuser:appgroup /data
exec su-exec appuser "$@"
D) Use POSIX ACLs on the host:
# Grant container UID access without changing ownership
setfacl -R -m u:999:rwx /path/to/host/directory
setfacl -R -d -m u:999:rwx /path/to/host/directory
3. Bind Mount Shows Empty Directory
Symptom: After mounting a host directory into a container, the container sees an empty directory even though the container image has files at that path.
Root Cause: Bind mounts overlay the container filesystem. The host directory replaces whatever was in the container image at that path.
Solution: Use named volumes instead. Docker volumes copy the image contents into the volume on first use:
# Named volume — image contents are preserved on first mount
docker run -d -v myvolume:/usr/share/nginx/html nginx
# Bind mount — host directory replaces image contents (empty if host dir is empty)
docker run -d -v ./empty-dir:/usr/share/nginx/html nginx # → empty!
If you must use bind mounts, pre-populate the host directory:
# Extract image contents to host directory first
docker create --name temp nginx
docker cp temp:/usr/share/nginx/html/. ./my-html/
docker rm temp
# Now bind mount the populated directory
docker run -d -v ./my-html:/usr/share/nginx/html nginx
4. Orphaned Volumes Consuming Disk Space
Symptom: The Docker host runs out of disk space. /var/lib/docker/volumes is consuming gigabytes.
Diagnostic:
# List all volumes
docker volume ls
# Show volumes not referenced by any container
docker volume ls -f dangling=true
# Check total disk usage
docker system df
Solution:
# Remove all dangling (orphaned) volumes
docker volume prune
# Remove ALL unused volumes (including named ones not referenced)
docker volume prune -a
# Remove a specific volume
docker volume rm myoldvolume
Warning:
docker volume prune -awill remove named volumes too if no container references them. Always verify withdocker volume ls -f dangling=truefirst.
5. Volume Data Not Syncing on macOS/Windows
Symptom: File changes on the host are not reflected inside the container (or vice versa) when using bind mounts on macOS or Windows.
Root Cause: Docker Desktop runs containers inside a lightweight Linux VM. Bind mounts must pass through a filesystem sharing layer (gRPC-FUSE, VirtioFS, or osxfs) which introduces latency and synchronization delays.
Solutions:
- Use VirtioFS (Docker Desktop 4.15+): In Docker Desktop settings, enable “VirtioFS” under the “General” tab. This is significantly faster than the legacy osxfs.
- Use named volumes for large datasets: Named volumes live inside the VM and have native Linux I/O performance.
- Use
docker-compose watch(Compose 2.22+): Enables efficient file synchronization for development workflows.
Prevention and Best Practices
- Always use named volumes for databases and stateful services. Never rely on bind mounts for production data persistence.
- Never use
docker-compose down -vin production. This deletes all volumes. Usedownwithout-v. - Back up volumes regularly: Use
docker run --rm -v myvolume:/data -v $(pwd):/backup busybox tar czf /backup/volume-backup.tar.gz -C /data . - Label your volumes: Use
docker volume create --label env=production myvolumeto organize and identify volumes. - Set
read-onlymounts where possible: Use:rosuffix (-v myvolume:/data:ro) for volumes that should not be written to, reducing the attack surface. - Monitor disk usage: Periodically run
docker system dfand set up alerts on/var/lib/dockerdisk usage.
Summary
- Docker containers are ephemeral — always use volumes or bind mounts for persistent data.
- Permission denied errors arise from UID mismatches between the container process and the mounted path.
- Named volumes are preferred over bind mounts because Docker manages permissions and preserves image contents.
- Clean up orphaned volumes with
docker volume pruneto recover disk space. - On macOS/Windows, use VirtioFS for better bind mount performance.