Here is a problem every Docker beginner runs into sooner or later: you run a database container, add some data, stop the container, start it again — and the data is gone. Everything you stored has vanished. This happens because containers are stateless by default. Docker volumes are the solution to this problem, and understanding them is essential for any real-world Docker usage.
When you create a container, Docker gives it a writable layer on top of the read-only image layers. Any files you write inside the container go into this writable layer. When the container is stopped and removed, that writable layer is deleted along with it. The image itself is unchanged — it is the container's individual state that disappears.
For stateless applications like web servers that do not store data locally, this is fine. But for databases, log files, uploaded files, or any data that must survive container restarts, you need a way to store data outside the container's lifecycle. That is exactly what volumes provide.
Docker gives you three storage options, each suited for different situations:
Named volumes are the recommended approach for production data. Docker manages their location and lifecycle independently of containers.
# Create a named volume
docker volume create my-data
# List all volumes
docker volume ls
# Inspect a volume (find where it is stored)
docker volume inspect my-data
# Run container with named volume
docker run -d --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword -v my-data:/var/lib/postgresql/data postgres:15
# The format is: -v volume_name:container_path
Now if you stop and remove the container, then create a new one using the same volume, all your database data will still be there. The volume persists independently of any container.
Bind mounts map a host directory directly into the container. This is extremely useful during development because any change you make to files on your host is immediately visible inside the container — no rebuild required.
# Map current directory into container
docker run -d -p 5000:5000 -v $(pwd):/app --name dev-server my-python-app:1.0
# On Windows PowerShell
docker run -d -p 5000:5000 -v ${PWD}:/app my-python-app:1.0
With this setup, you edit code on your local machine using your preferred editor, and the changes are instantly available inside the running container. This makes local development with Docker smooth and fast.
Let us run a MySQL database with persistent storage:
# Run MySQL with named volume
docker run -d --name mysql-db -e MYSQL_ROOT_PASSWORD=rootpass -e MYSQL_DATABASE=myapp -e MYSQL_USER=appuser -e MYSQL_PASSWORD=apppass -p 3306:3306 -v mysql-data:/var/lib/mysql mysql:8.0
# Connect to MySQL inside the container
docker exec -it mysql-db mysql -u root -p
# Stop and remove the container
docker stop mysql-db
docker rm mysql-db
# Start a NEW container using the SAME volume
docker run -d --name mysql-db-new -e MYSQL_ROOT_PASSWORD=rootpass -p 3306:3306 -v mysql-data:/var/lib/mysql mysql:8.0
# Your data is still there!
Multiple containers can mount the same volume, enabling them to share data. This is useful for scenarios like a web application and a background worker both needing access to uploaded files.
# Both containers share the same uploads volume
docker run -d --name web-app -v uploads:/app/uploads my-web:1.0
docker run -d --name worker -v uploads:/app/uploads my-worker:1.0
Since volumes contain important data, knowing how to back them up is critical. Docker does not have a built-in backup command, but this pattern works reliably:
# Backup: create a tar archive of the volume
docker run --rm -v my-data:/source -v $(pwd):/backup ubuntu tar czf /backup/my-data-backup.tar.gz -C /source .
# Restore: extract archive back into volume
docker run --rm -v my-data:/target -v $(pwd):/backup ubuntu tar xzf /backup/my-data-backup.tar.gz -C /target
# Remove a specific volume (only if no container uses it)
docker volume rm my-data
# Remove all unused volumes
docker volume prune
Warning: Removing a volume permanently deletes all data it contains. Always back up before removing production volumes.
In Part 5, we will learn Docker Networking — how containers talk to each other and to the outside world. This is essential for multi-container applications.
Docker supports two main types of mounts, each suited to different use cases. Bind mounts map a specific path on the host machine into the container: docker run -v /host/path:/container/path image. They are useful for development — mounting your source code directory into a container so code changes are reflected immediately without rebuilding. Named volumes are managed entirely by Docker: docker run -v myvolume:/container/path image. Docker creates and manages the storage location. Named volumes are the right choice for persistent application data in production — they are not dependent on host directory structure and are easier to backup and migrate.
# Named volume for database persistence
docker run -d --name postgres_db -v postgres_data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:15
# Bind mount for development (reflects local changes immediately)
docker run -d --name myapp_dev -v $(pwd)/src:/app/src -p 3000:3000 myapp:dev
# Inspect a volume
docker volume inspect postgres_data
# Backup a volume
docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz /data
Run a PostgreSQL container with a named volume. Connect to it and create a database with some test data. Stop and remove the container. Start a new PostgreSQL container using the same named volume and verify your data is still there. This exercise demonstrates the fundamental purpose of volumes — data that outlives the container that created it.