Docker Volumes - Persistent Data Management
Status: Active
Last Updated: 2026-01-30
Category: Containers - Storage
Prerequisites: docker-basics, docker-concepts
Time: 2-3 hours
Tags: docker, volumes, storage, persistence, data
Summary
Master Docker data persistence with volumes, bind mounts, and tmpfs mounts. Learn when to use each storage type, how to manage volumes, backup/restore strategies, and solve common data persistence challenges in containerized applications.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ Understand Docker storage types (volumes, bind mounts, tmpfs)
- โ Create and manage volumes
- โ Use volumes in containers
- โ Backup and restore volume data
- โ Share volumes between containers
- โ Understand volume drivers
- โ Implement proper data persistence strategies
๐ค The Storage Problem
Container Filesystem is Ephemeral:
docker run --name test-db postgres:15
# Database stores data in container
docker rm test-db
# Data is GONE forever! ๐
The Problem:
- Container storage disappears when container is removed
- Can't share data between containers easily
- Performance issues with container layers
- Backup/restore is complex
The Solution: Docker volumes and mounts!
๐ฆ Three Storage Types
Quick Comparison
| Type | Location | Use Case | Performance | Managed By |
|---|---|---|---|---|
| Volume | Docker area | Production data | Best | Docker |
| Bind Mount | Any host path | Development, config | Good | You |
| tmpfs | Memory | Temporary/secrets | Fastest | Docker |
Visual:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Host Machine โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ /var/lib/docker/volumes/ โ โ
โ โ โโโ my-volume/ โ โ
โ โ โโโ _data/ โ Volume (managed) โโโโโโผโโ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ /home/user/data/ โ Bind mount (your path)โโโโโโผโโค
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ RAM โ tmpfs (memory only, temporary) โโโโโโผโโค
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ Container โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ /var/lib/postgresql/data โ Mount point โโโโโโผโโ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐๏ธ Volumes (Recommended for Production)
Volumes: Managed by Docker, stored in Docker's area.
Benefits:
- Managed by Docker (easy backup/restore)
- Work on Windows, Mac, Linux
- Can use volume drivers (network storage, etc.)
- Isolated from host filesystem
- Safe to share between containers
Create Volume
# Create named volume
docker volume create my-data
# Create with driver options
docker volume create --driver local \
--opt type=none \
--opt device=/mnt/storage \
--opt o=bind \
my-data
List Volumes
# List all volumes
docker volume ls
# Output:
DRIVER VOLUME NAME
local my-data
local postgres-data
local abc123def456 โ Anonymous volume
Inspect Volume
# Get volume details
docker volume inspect my-data
# Output:
[
{
"CreatedAt": "2026-01-30T10:00:00Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-data/_data",
"Name": "my-data",
"Options": {},
"Scope": "local"
}
]
Use Volume in Container
# Mount volume to container
docker run -d \
--name my-postgres \
-v my-data:/var/lib/postgresql/data \
postgres:15
# Or with --mount (more explicit)
docker run -d \
--name my-postgres \
--mount source=my-data,target=/var/lib/postgresql/data \
postgres:15
Syntax:
-v VOLUME_NAME:CONTAINER_PATH
--mount source=VOLUME_NAME,target=CONTAINER_PATH
Volume Persists After Container Removal
# Create container with volume
docker run -d --name db1 -v db-data:/var/lib/postgresql/data postgres:15
# Write some data
docker exec db1 psql -U postgres -c "CREATE DATABASE testdb;"
# Remove container
docker rm -f db1
# Volume still exists!
docker volume ls | grep db-data
# Create new container with same volume
docker run -d --name db2 -v db-data:/var/lib/postgresql/data postgres:15
# Data is still there!
docker exec db2 psql -U postgres -l | grep testdb
Remove Volume
# Remove volume (must not be in use)
docker volume rm my-data
# Remove all unused volumes
docker volume prune
# Force remove (be careful!)
docker volume prune -f
๐ Bind Mounts (Development & Config)
Bind Mount: Mount any host directory into container.
Benefits:
- Access files on host immediately
- Edit files with host tools
- Good for development
- Good for config files
Drawbacks:
- Host path must exist
- Less portable (path may not exist on other machines)
- Permission issues possible
Basic Bind Mount
# Create directory on host
mkdir -p ~/my-website
# Create index file
echo "<h1>Hello from Host!</h1>" > ~/my-website/index.html
# Mount into Nginx container
docker run -d \
--name web \
-p 8080:80 \
-v ~/my-website:/usr/share/nginx/html:ro \
nginx:latest
# Test
curl http://localhost:8080
# Output: <h1>Hello from Host!</h1>
# Edit file on host
echo "<h1>Updated!</h1>" > ~/my-website/index.html
# Immediately visible in container
curl http://localhost:8080
# Output: <h1>Updated!</h1>
Syntax:
-v /host/path:/container/path[:options]
Options:
:ro - Read-only
:rw - Read-write (default)
Bind Mount for Config Files
# Create custom nginx config
cat > ~/nginx.conf << 'EOF'
server {
listen 80;
server_name example.com;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
EOF
# Mount config file
docker run -d \
--name web \
-p 8080:80 \
-v ~/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
-v ~/my-website:/usr/share/nginx/html:ro \
nginx:latest
Development Workflow Example
# Project structure
my-app/
โโโ src/
โ โโโ app.py
โโโ requirements.txt
# Run with bind mount for live reload
docker run -d \
--name dev-app \
-p 5000:5000 \
-v $(pwd)/src:/app/src \
-e FLASK_ENV=development \
my-python-app:dev
# Edit src/app.py on host
# Changes immediately visible in container
# Flask auto-reloads
๐จ tmpfs Mounts (Temporary Data)
tmpfs: Mount in container's memory (RAM).
Benefits:
- Extremely fast (in RAM)
- Data never written to disk (security!)
- Automatically cleaned up
Use Cases:
- Temporary files
- Sensitive data (passwords during processing)
- High-performance scratch space
- Cache that doesn't need persistence
Create tmpfs Mount
# Mount tmpfs
docker run -d \
--name app \
--tmpfs /tmp:rw,size=100m \
my-app:latest
# Or with --mount
docker run -d \
--name app \
--mount type=tmpfs,target=/tmp,tmpfs-size=104857600 \
my-app:latest
Options:
size: Max size (e.g., 100m, 1g)mode: Permissions (e.g., 1777)
๐ Sharing Volumes Between Containers
Share Named Volume
# Create volume
docker volume create shared-data
# Container 1 writes data
docker run -d \
--name writer \
-v shared-data:/data \
alpine sh -c "while true; do echo $(date) >> /data/log.txt; sleep 5; done"
# Container 2 reads data
docker run -d \
--name reader \
-v shared-data:/data:ro \
alpine sh -c "while true; do tail -f /data/log.txt; done"
# Both access same data!
docker logs reader
Volumes-From (Legacy Pattern)
# Data container
docker run -d \
--name data-container \
-v /data \
alpine sleep infinity
# App container uses volumes from data-container
docker run -d \
--name app \
--volumes-from data-container \
my-app:latest
Note: Named volumes (previous method) are preferred now.
๐พ Backup and Restore
Backup Volume
# Create volume with data
docker volume create important-data
docker run --rm -v important-data:/data alpine sh -c "echo 'Important stuff' > /data/file.txt"
# Backup volume to tar file
docker run --rm \
-v important-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/backup.tar.gz -C /data .
# Backup file created: backup.tar.gz
Restore Volume
# Create new volume
docker volume create restored-data
# Restore from backup
docker run --rm \
-v restored-data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/backup.tar.gz -C /data
# Verify
docker run --rm -v restored-data:/data alpine cat /data/file.txt
# Output: Important stuff
Automated Backup Script
#!/bin/bash
# backup-volume.sh
VOLUME_NAME=$1
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
docker run --rm \
-v ${VOLUME_NAME}:/data \
-v ${BACKUP_DIR}:/backup \
alpine tar czf /backup/${VOLUME_NAME}_${DATE}.tar.gz -C /data .
echo "Backup created: ${BACKUP_DIR}/${VOLUME_NAME}_${DATE}.tar.gz"
Usage:
chmod +x backup-volume.sh
./backup-volume.sh postgres-data
๐ฏ Real-World Examples
Example 1: PostgreSQL with Persistent Data
# Create volume for database
docker volume create postgres-data
# Run PostgreSQL
docker run -d \
--name postgres-prod \
-e POSTGRES_PASSWORD=secret123 \
-e POSTGRES_DB=production \
-v postgres-data:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:15
# Create some data
docker exec postgres-prod psql -U postgres -d production -c \
"CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(100));"
docker exec postgres-prod psql -U postgres -d production -c \
"INSERT INTO users (name) VALUES ('Alice'), ('Bob');"
# Restart container (data persists)
docker restart postgres-prod
# Even remove and recreate (data still there!)
docker rm -f postgres-prod
docker run -d \
--name postgres-prod-new \
-e POSTGRES_PASSWORD=secret123 \
-v postgres-data:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:15
# Data still exists
docker exec postgres-prod-new psql -U postgres -d production -c "SELECT * FROM users;"
Example 2: WordPress with Separate Volumes
# Create volumes
docker volume create wp-db-data
docker volume create wp-content
# Run MySQL
docker run -d \
--name wp-db \
-e MYSQL_ROOT_PASSWORD=rootpass \
-e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wpuser \
-e MYSQL_PASSWORD=wppass \
-v wp-db-data:/var/lib/mysql \
mysql:8
# Run WordPress
docker run -d \
--name wordpress \
--link wp-db:mysql \
-e WORDPRESS_DB_HOST=mysql \
-e WORDPRESS_DB_USER=wpuser \
-e WORDPRESS_DB_PASSWORD=wppass \
-e WORDPRESS_DB_NAME=wordpress \
-v wp-content:/var/www/html/wp-content \
-p 8080:80 \
wordpress:latest
# Both database and uploads persist!
Example 3: Development with Bind Mounts
# Project directory
my-flask-app/
โโโ app.py
โโโ templates/
โโโ static/
# Run with bind mounts for live development
docker run -d \
--name flask-dev \
-p 5000:5000 \
-v $(pwd)/app.py:/app/app.py \
-v $(pwd)/templates:/app/templates \
-v $(pwd)/static:/app/static \
-e FLASK_ENV=development \
-e FLASK_DEBUG=1 \
my-flask-app:dev
# Edit files on host โ immediately reflected in container
Example 4: Nginx with Config and Logs
# Create directories
mkdir -p ~/nginx/{conf,html,logs}
# Create config
cat > ~/nginx/conf/nginx.conf << 'EOF'
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
EOF
# Create website
echo "<h1>My Site</h1>" > ~/nginx/html/index.html
# Run with multiple bind mounts
docker run -d \
--name nginx-custom \
-p 8080:80 \
-v ~/nginx/conf/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
-v ~/nginx/html:/usr/share/nginx/html:ro \
-v ~/nginx/logs:/var/log/nginx \
nginx:latest
# View logs on host
tail -f ~/nginx/logs/access.log
๐ Volume Drivers
Volume Drivers: Enable different storage backends.
Local Driver (Default)
# Default driver
docker volume create my-volume
# Explicit local driver
docker volume create --driver local my-volume
NFS Driver Example
# Create NFS volume
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/path/to/nfs/share \
nfs-volume
# Use in container
docker run -d -v nfs-volume:/data alpine
Third-Party Drivers
Popular Volume Drivers:
- REX-Ray: Cloud storage (AWS EBS, Azure Disk)
- Portworx: Enterprise container storage
- GlusterFS: Distributed filesystem
- Ceph: Distributed storage
# Install plugin
docker plugin install rexray/ebs
# Create volume with driver
docker volume create --driver rexray/ebs my-ebs-volume
โ ๏ธ Common Pitfalls
1. Permission Issues
Problem:
# Container runs as non-root (UID 1000)
# Volume owned by root (UID 0)
# Container can't write!
Solution:
# Option 1: Change ownership before mounting
sudo chown -R 1000:1000 /host/path
# Option 2: Use USER in Dockerfile
FROM python:3.11
RUN useradd -m -u 1000 appuser
USER appuser
# Option 3: Use :z or :Z flag (SELinux)
docker run -v /host:/container:z myimage
2. Anonymous Volumes
Problem:
# Dockerfile has VOLUME instruction
FROM postgres:15
VOLUME /var/lib/postgresql/data
# Running without -v creates anonymous volume
docker run postgres:15
# Anonymous volume created: abc123def456
# Hard to manage, left behind after container removal
Solution: Always use named volumes!
docker run -v postgres-data:/var/lib/postgresql/data postgres:15
3. Mounting Over Existing Data
Problem:
# Container has files in /app/data
# Mounting volume to /app/data hides those files
docker run -v my-volume:/app/data myimage
# Original /app/data contents invisible!
Solution:
- Copy data from image to volume first
- Or mount to different path
4. Volume Not Created Before Use
Problem:
# Volume doesn't exist
docker run -v non-existent:/data alpine
# Docker creates it (empty!)
# But you expected existing data
Solution: Create volumes explicitly
docker volume create my-volume
docker run -v my-volume:/data alpine
๐ ๏ธ Best Practices
1. Use Named Volumes for Production
# Good
docker volume create prod-db-data
docker run -v prod-db-data:/var/lib/postgresql/data postgres:15
# Bad (anonymous volume)
docker run -v /var/lib/postgresql/data postgres:15
2. Bind Mounts for Development Only
# Development: OK
docker run -v $(pwd)/src:/app/src my-app:dev
# Production: Use volumes or COPY in Dockerfile
docker run -v app-data:/app/data my-app:prod
3. Read-Only Mounts When Possible
# Config files should be read-only
docker run -v ~/config.yaml:/app/config.yaml:ro my-app
# Prevents container from modifying config
4. Label Volumes
# Create with labels
docker volume create \
--label project=myapp \
--label environment=production \
--label backup=daily \
myapp-prod-data
# Find volumes by label
docker volume ls --filter label=project=myapp
5. Regular Backups
# Automated backup with cron
0 2 * * * /scripts/backup-volumes.sh
# Script backs up all volumes with label backup=daily
๐ What's Next?
Now that you understand Docker storage:
Networking:
- docker-networking - Container networking deep dive
Multi-Container:
- docker-compose-intro - Define multi-container apps
Advanced Volumes:
- docker-volume-drivers - Custom storage backends
๐ Resources
Official Docs:
Tools:
๐ Change Log
2026-01-30
- Created Docker volumes article
- Covered three storage types (volumes, bind mounts, tmpfs)
- Explained volume management commands
- Included backup/restore strategies
- Provided real-world examples (Postgres, WordPress, development)
- Added troubleshooting for common issues
- Included best practices for production
Next Article: docker-networking - Master container networking!