Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Docker Volumes for Persistent Data

Learn how to use Docker volumes to persist data across container restarts, with a hands-on exercise and practical troubleshooting tips.

Focus: docker volumes persistent data

Sponsored

You've built a Docker image, started a container, and maybe even cleaned up after it. But when you ran docker rm or updated the image, the data inside the container — that database, that uploaded file, that log line — vanished. That's the core pain of ephemeral containers: by default, they have no memory. In this lesson, you'll learn to use Docker volumes for persistent data, a mechanism that decouples your data from the container lifecycle. By the end, you'll be able to run stateful containers that survive restarts, updates, and full system reboots without losing a byte.

The problem this lesson solves

Containers are ephemeral by design. When you stop and remove a container (docker rm), Docker discards the container's filesystem — including any files created or modified at runtime. This is fine for stateless microservices that only serve API requests, but it's a disaster for databases, CMS applications, uploaded media folders, or any application that needs to remember state between runs.

Consider a PostgreSQL container: if you docker run postgres without a volume, the moment you kill that container, the entire database — tables, rows, indexes — is gone. Even docker restart isn't the issue; the problem is that the data lives only inside the container's writable layer. That layer is tied to the container's lifecycle. You cannot share it with another container. You cannot easily back it up. And when you update the image, you lose everything.

This is why Docker volumes for persistent data exist. They provide a dedicated storage area on the host filesystem, managed by Docker, that lives independently of any single container. Once you attach a volume, data inside it persists even if you delete the container.

Core concept / mental model

Think of a Docker container as a disposable house made of paper. It's cheap, fast to build, easy to tear down. But if you want to store important documents (your data), you put them in a fireproof safe bolted to the concrete floor. When the paper house burns down, the safe remains. You can build a new house around the same safe, and your documents are still there.

In Docker terms: - Container: the disposable house. - Volume: the fireproof safe. - Mount: the act of bolting the safe to the floor (attaching the volume to a specific path inside the container).

Docker volumes are stored on the host filesystem under /var/lib/docker/volumes/ (Linux) or a similar path on Windows/Mac. You create them with docker volume create, assign them with the -v or --mount flag during docker run, and they persist until you explicitly delete them.

Definitions

  • Named volume: a volume with a human-readable name you specify (e.g., my-data). Easy to manage and reference.
  • Anonymous volume: a volume with a random hash name, created automatically (e.g., with -v /data). Harder to track but useful for quick tests.
  • Bind mount: not a Docker volume, but a direct mapping of a host directory into the container (e.g., -v /host/path:/container/path). Different from volumes because the host path is managed by you, not Docker.

Pro tip: For production and most development scenarios, use named volumes. They are portable across Docker environments, do not depend on a specific host directory structure, and are easier to back up and inspect.

How it works step by step

  1. Create a volume (optional, but recommended for clarity): docker volume create my-volume. - Docker allocates a directory on the host under /var/lib/docker/volumes/my-volume/_data. This is the actual physical location of your data.
  2. Start a container with --mount (modern) or -v (short form) that attaches the volume to a target path inside the container: bash docker run -d --name my-app --mount source=my-volume,target=/app/data nginx:alpine - The container sees /app/data as a regular directory. Any files written there are actually written to the host's volume directory.
  3. Write data inside the container (e.g., using docker exec or application logic).
  4. Stop, restart, or remove the container — the volume persists.
  5. Start a new container with the same volume — the data is instantly available.

The 'mount' contract

  • source: the volume name (or host path for bind mounts).
  • target: the path inside the container's filesystem where the volume will appear.
  • The volume must have a name; if it doesn't exist, docker run with -v may create one automatically, but using --mount with a non-existent source fails.

Hands-on walkthrough

Example 1: Create and use a named volume

# Create a volume
docker volume create my-db-data

# Run a PostgreSQL container that uses the volume for its data directory
docker run -d --name postgres-db \
  -e POSTGRES_PASSWORD=secret \
  --mount source=my-db-data,target=/var/lib/postgresql/data \
  postgres:13

# Verify the container is running and data is being written (optional)
docker exec -it postgres-db psql -U postgres -c "CREATE TABLE test (id INT);"

# Stop and remove the container completely
docker stop postgres-db
docker rm postgres-db

# Start a new PostgreSQL container with the same volume
# All data remains!
docker run -d --name postgres-db-new \
  -e POSTGRES_PASSWORD=secret \
  --mount source=my-db-data,target=/var/lib/postgresql/data \
  postgres:13

# Verify the table still exists
docker exec -it postgres-db-new psql -U postgres -c "SELECT * FROM test;"
# Output: (0 rows) — meaning the table persisted

Example 2: Write and read a file across containers

# Create a volume
docker volume create shared-uploads

# Start container A and write a file
docker run -d --name writer \
  --mount source=shared-uploads,target=/uploads \
  alpine:latest sh -c "echo 'Hello from container A' > /uploads/hello.txt && sleep infinity"

# Stop and remove container A
docker stop writer && docker rm writer

# Start container B that reads the same file
docker run -it --name reader \
  --mount source=shared-uploads,target=/data \
  alpine:latest cat /data/hello.txt
# Output: Hello from container A

Example 3: Use the short -v syntax (equivalent to example 2)

# Create volume (optional; -v can auto-create)
docker volume create quick-share

# Run with -v (source:target)
docker run -d --name temp-writer -v quick-share:/app alpine sh -c "echo 'quick data' > /app/log.txt && sleep 5"
sleep 6

# Check if file persisted (container is dead, but volume lives)
docker run --rm -v quick-share:/app alpine cat /app/log.txt
# Output: quick data

Compare options / when to choose what

Feature Named Volume Bind Mount (host path) tmpfs Mount (in-memory)
Persistence Survives container lifecycle — persists until explicitly deleted Survives container lifecycle — file is on host Lost on container stop (in memory)
Host location Managed by Docker (/var/lib/docker/volumes/...) Any absolute path you specify (/home/user/data) Kernel memory (no host Path)
Portability High — works across environments (CI, dev, production) Low — depends on specific host path structure Not portable
Performance Good — native Docker filesystem Good — direct host I/O (slightly faster for large files) Very fast — no disk write
Management Easy — docker volume ls, docker volume rm Manual — you manage host path No management
Backup docker run --rm -v vol-name:/source alpine tar -czf /backup.tar.gz -C /source . Direct file copy on host Not applicable
Best for Databases, persistent application data, shared config Development — real-time code sync, special host tools Secrets or very high-speed temporary files

Pro tip: Avoid bind mounts in production unless you absolutely control the host filesystem. Use named volumes for most persistent data — they are easier to backup, migrate, and scale.

Troubleshooting & edge cases

Volume is empty when container starts

  • Cause: You mounted a volume at a target path that the container's image already has data in (e.g., /var/lib/postgresql/data for PostgreSQL). Docker copies existing image content into the volume only when the volume is empty and the container is started fresh. If the volume already has data from a previous run (or was created manually), no copy happens.
  • Fix: Delete the volume (docker volume rm my-volume) and recreate it, then start the container fresh. Or, use a bind mount to the same directory to manually check what's inside.

Permission denied (e.g., "Permission denied" when writing to volume)

  • Cause: The container's user (often root for many images, but not all — e.g., nginx runs as nginx user) doesn't have write permissions to the volume directory on host. This is more common with bind mounts.
  • Fix: Use named volumes (Docker manages permissions). If you must use bind mounts, ensure the host directory has appropriate permissions or run the container with a specific user (docker run --user 1000:1000 ...).

Volume is not visible after restart

  • Cause: You used an anonymous volume (-v /data without a name) and then started a new container without specifying the same anonymous volume. Docker creates a new anonymous volume each time.
  • Fix: Always use named volumes (-v volume-name:/data) when you need persistence across container restarts.

“volume is in use” when deleting

  • Cause: A running or stopped container still references the volume. Docker prevents accidental data loss.
  • Fix: Remove all containers that use the volume (docker rm -f container_name) or use docker volume rm -f (force flag, but still fails if containers exist).

Example: Error scenario

# Assume container 'my-app' is still running
docker volume rm my-db-data
# Error Response from daemon: remove my-db-data: volume is in use - [my-app]

What you learned & what's next

You now understand how to use Docker volumes for persistent data — a foundational skill for running stateful applications in containers. You learned: - The difference between ephemeral container storage and persistent volumes. - How to create named volumes and attach them to containers using both --mount and -v syntax. - How to verify data persists across container lifetimes. - When to choose named volumes over bind mounts and tmpfs. - Common pitfalls like empty volumes and permission errors.

With this knowledge, you can confidently run databases, file storage services, and any other stateful workload in Docker. The next lesson builds on this: Managing data in multi-container applications with Docker Compose volumes. You'll learn how to declare volumes in a docker-compose.yml file, share them between services, and use external volumes for production environments.

Common mistakes

  • Using anonymous volumes (-v /data) when you want persistence — each new container creates a new anonymous volume, losing previous data.
  • Forgetting to remove old containers that still reference a volume before deleting the volume, resulting in 'volume is in use' errors.
  • Mounting a volume at a path where the image already has data, then expecting the image's default data to appear after the volume is attached — Docker only copies image content to empty volumes.
  • Confusing bind mounts with named volumes: bind mounts tie you to a host directory and are less portable, but are often used by mistake for persistent data.

Variations

  1. Use docker compose volumes for declarative, multi-service persistent storage with named volumes defined in YAML.
  2. Use --volume-driver to use external storage backends like NFS, cloud storage (e.g., Azure File Share, AWS EFS), or encrypted volumes.
  3. Use tmpfs mounts for temporary, high-speed data that must not be stored on disk at all.

Real-world use cases

  • Running a PostgreSQL or MySQL database in a container where all table data and logs must survive container restarts and image updates.
  • Hosting a Django or Rails application where user-uploaded images and media files are stored in a shared volume accessible by multiple container replicas.
  • Running an Elasticsearch or Redis container that needs to persist indices or cache data across host reboots and container recreations.

Key takeaways

  • Docker volumes are managed by Docker and persist independently of container lifecycles, solving the ephemeral storage problem.
  • Created named volumes with docker volume create and attach them via --mount source=volume-name,target=/path or -v volume-name:/path.
  • Volume data survives docker rm of the container and can be shared among multiple containers.
  • Named volumes are more portable and easier to manage than bind mounts for production workloads.
  • Always back up your volumes; they can be exported using a temporary container and tar.
  • When a volume is reused, Docker does not copy default image data into it — empty volumes get initial data, reused volumes do not.

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.