Docker Volumes for Persistent Data
Master Docker volumes to persist data between container restarts and removals. Learn how bind mounts, named volumes, and tmpfs mounts work, with hands-on exercises and edge-case troubleshooting.
Focus: Docker volumes persistent data
Containers are ephemeral by design — every time you restart a container or replace it with a new image, any data written inside its filesystem vanishes. That’s great for stateless microservices but a nightmare for databases, user uploads, or application state. Docker volumes solve this by providing persistent storage that outlives any single container, letting you keep your data safe across deployments, updates, and scaling events. This lesson will give you a mental model for Docker’s storage options, walk you through the step-by-step mechanics, and equip you to choose the right volume type for any scenario.
The problem this lesson solves
When you run a container without any storage configuration, all data written to /data, /var/lib/mysql, or anywhere else lives inside the container’s writable layer. This layer is tied to the container’s lifecycle:
- Container removed → data lost.
- Container recreated (e.g.,
docker runwith a new image tag) → data lost. - Scaling replicas → each instance has its own isolated copy.
In production, you need databases to survive restarts, log files to be accessible from the host, and configuration to be shared across multiple containers. Without volumes, you’d have to manually copy data out before every deployment — error-prone and wasteful. Docker volumes are the built-in mechanism to decouple persistent data from container lifecycles.
Core concept / mental model
Think of a Docker volume as a USB drive that you plug into a container. The drive itself lives on the host (or a network location), but the container sees it as a normal directory. When the container is destroyed, the USB drive remains, and you can plug it into a different container later.
Docker offers three primary types of storage mounts:
- Named volumes — managed by Docker, stored in
/var/lib/docker/volumes/on the host. Best for data you don’t need to access directly from the host filesystem (databases, app state). - Bind mounts — mount any host directory into the container. Great for sharing source code during development or configuration files.
- tmpfs mounts — stored only in memory; never written to disk. Useful for sensitive data (secrets) or temporary files that should vanish on restart.
| Feature | Named Volume | Bind Mount | tmpfs |
|---|---|---|---|
| Persistent on host | ✅ Yes | ✅ Yes | ❌ No (in memory) |
Manageable via docker volume |
✅ create, ls, rm |
❌ Direct host path | ❌ Not managed |
| Share across containers | ✅ Yes | ✅ Yes | ❌ Per-container |
| Empty directory initialization | ✅ Volume content copied | ❌ Host directory content used | ✅ Empty |
| Performance | Good | Good | Very fast |
How it works step by step
Step 1: Create a named volume (or let Docker create it on the fly)
You can explicitly create a volume:
docker volume create my-data
Or have Docker create it automatically when you run a container with the -v or --mount flag.
Step 2: Mount the volume into a container
When you run a container, attach the volume to a specific path:
docker run -d --name my-app -v my-data:/app/data nginx:latest
Now /app/data inside the container points to the my-data volume on the host.
Step 3: Write data inside the container
Any data written to /app/data is persisted on the volume, even if the container stops or is removed.
Step 4: Reuse the volume with a new container
docker rm my-app
docker run -d --name my-app-v2 -v my-data:/app/data nginx:latest
The new container sees exactly the same data — no loss.
Hands-on walkthrough
Let’s put this into practice. You’ll create a named volume for a PostgreSQL database and verify that data persists across container restarts.
Example 1: Named volume for PostgreSQL
# Create a volume
docker volume create pgdata
# Run PostgreSQL with the volume mounted to /var/lib/postgresql/data
docker run -d \
--name postgres-db \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:13
# Check the volume is mounted
docker inspect postgres-db | grep -A 5 Mounts
Expected output (simplified):
"Mounts": [
{
"Type": "volume",
"Name": "pgdata",
"Source": "/var/lib/docker/volumes/pgdata/_data",
"Destination": "/var/lib/postgresql/data"
}
]
Now connect to the database, create a table, and insert a row:
docker exec -it postgres-db psql -U postgres -c "CREATE TABLE test (id int, name text);"
docker exec -it postgres-db psql -U postgres -c "INSERT INTO test VALUES (1, 'hello');"
Example 2: Verify persistence
# Stop and remove the container
docker stop postgres-db
docker rm postgres-db
# Start a new container with the same volume
docker run -d \
--name postgres-db-new \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:13
# Query the data — it’s still there!
docker exec -it postgres-db-new psql -U postgres -c "SELECT * FROM test;"
Expected output:
id | name
----+-------
1 | hello
(1 row)
Example 3: Bind mount for configuration
# On the host
mkdir ~/nginx-config
cat > ~/nginx-config/default.conf <<EOF
server {
listen 80;
server_name example.com;
location / {
root /usr/share/nginx/html;
}
}
EOF
# Run NGINX with a bind mount
docker run -d \
--name nginx-custom \
-v ~/nginx-config:/etc/nginx/conf.d:ro \
nginx:latest
Pro tip: Bind mounts use the host path directly. Always use absolute paths, or Docker will treat a relative path as a volume name — a common pitfall.
Compare options / when to choose what
| Storage Type | Best Use Case | Lifecycle | Management |
|---|---|---|---|
| Named Volume | Databases, persistent app state, shared caches | Survives container restart/removal | Use docker volume commands. Auto-created if missing |
| Bind Mount | Development code sharing, config files, host logs | Survives container restart/removal | Host path directly. Must exist or Docker creates a directory |
| tmpfs Mount | Secrets, session data, temporary files | Lost on container stop | Mounted with --tmpfs or --mount type=tmpfs |
When to choose each:
- Named volume when you want Docker to manage the storage location and not worry about host paths. Ideal for production databases.
- Bind mount when you need direct access from the host, such as editing code in an IDE and having it reflected inside the container instantly.
- tmpfs when data must never touch disk — security-sensitive values or ephemeral processing.
Troubleshooting & edge cases
Common mistake: Using a relative path as a volume name
# WRONG — Docker treats 'my-data' as a volume name (not a bind mount)
docker run -v my-data:/app/data ...
# RIGHT — use absolute path for bind mount
docker run -v $(pwd)/my-data:/app/data ...
Edge case: Volume permissions
If the container runs as a non-root user, the volume may not be writable. Solution: ensure the host directory or volume has correct ownership, or set the user explicitly:
docker run -u 1000:1000 -v data:/app/data ...
Edge case: Empty volume initialization
When you mount an empty named volume to a directory that already has files in the image, Docker copies the image’s contents into the volume. This does not happen for bind mounts — the host directory takes precedence.
Debugging mounts
Use docker inspect to see mount details:
docker inspect <container-name> | jq '.[].Mounts[]'
List all volumes:
docker volume ls
Remove unused volumes to free disk space:
docker volume prune
What you learned & what's next
You now understand how Docker volumes provide persistent data storage, how to choose between named volumes, bind mounts, and tmpfs mounts, and how to verify data survival across container lifecycles. You’ve completed a hands-on exercise with PostgreSQL and NGINX, and you can troubleshoot common volume-related issues.
Next step: Learn how to share data between multiple containers using Docker Compose volumes, or dive into Docker networking to connect containers securely.
Practice recap
Create a named volume called my-app-logs. Run an NGINX container with this volume mounted to /var/log/nginx. Then access the container, write a log entry, stop the container, remove it, start a new container with the same volume, and confirm the log file still exists.
Common mistakes
- Using a relative path like
./datain-v ./data:/app— Docker interprets it as a named volume, not a bind mount. Always use absolute paths or$(pwd)/data. - Forgetting to stop a container before removing it and expecting data to survive — volumes survive, but the container writable layer does not.
- Mounting a volume to a non-empty directory and assuming the host directory content will be ignored — bind mounts obscure the image’s contents entirely.
- Running a container as a non-root user and getting permission errors on the volume — fix with
-u 1000:1000orchownthe host directory.
Variations
- Docker Compose volumes: define named volumes in
docker-compose.ymlundervolumes:for multi-service persistence. - Third-party volume drivers: use plugins like
vieux/sshfsor cloud-specific drivers (AWS EBS, Azure Disk) to mount remote storage. - Kubernetes volumes: the concept extends to PersistentVolumeClaims (PVCs) for cluster-wide persistent storage.
Real-world use cases
- Database containers (PostgreSQL, MySQL) using named volumes to survive pod rescheduling in production.
- Development workflow where bind mounts sync local source code changes instantly into a hot-reloading container.
- Log aggregation stack where containers write logs to a shared volume, then a log shipper reads and forwards them to a central server.
Key takeaways
- Named volumes are Docker-managed and ideal for persistent data that must survive container removal.
- Bind mounts mount any host directory into a container — great for development but require absolute paths.
- tmpfs mounts keep data in memory only, useful for secrets and temporary processing.
- Use
docker volume create,ls,inspect,pruneto manage storage lifecycle. - Empty named volumes are initialized with image content; bind mounts are not.
- Always verify mount details with
docker inspectwhen debugging persistent data issues.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.