Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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 run with 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 ./data in -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:1000 or chown the host directory.

Variations

  1. Docker Compose volumes: define named volumes in docker-compose.yml under volumes: for multi-service persistence.
  2. Third-party volume drivers: use plugins like vieux/sshfs or cloud-specific drivers (AWS EBS, Azure Disk) to mount remote storage.
  3. 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, prune to manage storage lifecycle.
  • Empty named volumes are initialized with image content; bind mounts are not.
  • Always verify mount details with docker inspect when debugging persistent data issues.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.