Docker Volume Hands-On Tutorial: Bind Mounts and Volumes Explained
Explore the practical implementation of Docker data persistence. This comprehensive guide covers why containers are inherently stateless, how data is lost upon container deletion, and how both Bind Mounts and Docker Volumes solve this problem across stateless web servers (Nginx) and stateful databases (MySQL).
1. The Core Problem: Stateless vs. Stateful Applications
Applications in modern software architecture are broadly categorized into two types based on how they handle data:
- Stateless Applications: Applications that do not persist data locally across sessions or restarts.
- Stateful Applications: Applications that store persistent data (such as user records, transactions, and configurations) that must survive restarts and crashes.
Containers by nature are stateless. When a container is deleted using docker rm -f, all modifications made inside its writable container layer are gone forever.
2. Nginx Without Volumes: The Danger of Container Deletion
To understand why data persistence is necessary, let's observe what happens when we run an Nginx container without any volume mounting.
Step-by-Step Walkthrough
- Pull and run Nginx:
BASH
docker pull nginx docker run -d -p 80:80 --name webserver nginx - Inspect and Test:
BASH
docker inspect webserver # Find IP address curl http://<container-ip> # Output: Welcome to nginx! (Default HTML page) - Modify file inside container:
BASH
docker exec -it webserver /bin/bash cd /usr/share/nginx/html rm -f index.html echo "This is without volume" > index.html exit - Delete the container:
BASH
docker rm -f webserver docker ps -a # Result: Container is gone, and the custom index.html file is permanently lost.
3. Method 1: Bind Mounts (Linking Host Directories)
A Bind Mount allows you to map a specific absolute directory path on your host machine directly into the container's filesystem directory.
Host Machine (/home/ubuntu/data)
^
| (Linked via Bind Mount: -v /host/path:/container/path)
v
Container (/usr/share/nginx/html)
Implementing an Nginx Bind Mount
-
Create a host directory:
BASHmkdir -p ~/data -
Run container with bind mount (
-v):BASHdocker run -d -p 80:80 -v /home/ubuntu/data:/usr/share/nginx/html --name web1 nginx -
Inspect the mount configuration:
BASHdocker inspect web1Note: Look for the
"Mounts"section in the JSON output. You will see"Type": "bind","Source": "/home/ubuntu/data", and"Destination": "/usr/share/nginx/html". -
Write data from inside the container and observe host synchronization:
BASHdocker exec -it web1 /bin/bash cd /usr/share/nginx/html echo "Hi, this is from docker volume, web 1" > index.html exit # Verify on host machine: cat ~/data/index.html # Output: Hi, this is from docker volume, web 1
Sharing Data Between Multiple Containers via Bind Mounts
Because both containers point to the exact same host directory (~/data), changes made by one container instantly reflect in the other.
# Spin up a second container (web2) pointing to the same host directory
docker run -d -p 8080:80 -v /home/ubuntu/data:/usr/share/nginx/html --name web2 nginx
# Append data from web2
docker exec -it web2 /bin/bash
echo "New line from web2 container" >> /usr/share/nginx/html/index.html
exit
# Check web1 or host file
cat ~/data/index.html
4. Method 2: Docker Volumes (Managed Storage)
While bind mounts require you to manage absolute host paths (e.g., /home/ubuntu/data), Docker Volumes are managed entirely by Docker and stored in a dedicated area on the host filesystem (/var/lib/docker/volumes/...).
Docker Managed Volume (/var/lib/docker/volumes/new_data/_data)
^
| (Referenced simply by volume name: -v new_data:/container/path)
v
Container (/usr/share/nginx/html)
Creating and Inspecting Docker Volumes
- Create a named volume:
BASH
docker volume create new_data - List volumes:
BASH
docker volume ls - Inspect volume storage path:
BASH
docker volume inspect new_dataNote: The
MountPointpoints to/var/lib/docker/volumes/new_data/_data. This is where Docker securely stores your files without requiring you to manually specify host directory paths.
Running Containers with Named Volumes
docker run -d --name web3 -v new_data:/usr/share/nginx/html nginx
5. Stateful Persistence: MySQL with Bind Mounts
Databases are prime examples of stateful applications. If a MySQL container crashes without a volume, all tables and records vanish.
MySQL Bind Mount Hands-On
-
Create a host database directory:
BASHmkdir -p ~/database -
Run MySQL container with bind mount & root password:
BASHdocker run -d \ -e MYSQL_ROOT_PASSWORD=admin \ -v /home/ubuntu/database:/var/lib/mysql \ --name db nginx-replacement-mysql # (or standard mysql image)Conceptually: The official MySQL image expects the
MYSQL_ROOT_PASSWORDenvironment variable (-e) and stores all database files inside/var/lib/mysql. -
Log into MySQL and create data:
BASHdocker exec -it db mysql -u root -p # Enter password: admin CREATE DATABASE testDB; USE testDB; CREATE TABLE persons (person_id INT, last_name VARCHAR(50), first_name VARCHAR(50)); INSERT INTO persons VALUES (1, 'Gore', 'Shubham'); SELECT * FROM persons; exit; -
Delete and Recreate Container:
BASHdocker rm -f db # Spin up a new container referencing the same host folder docker run -d \ -e MYSQL_ROOT_PASSWORD=admin \ -v /home/ubuntu/database:/var/lib/mysql \ --name web_wall mysqlNote on MySQL Password Gotchas: When mounting an existing MySQL data directory that already contains system tables (
mysqlschema), MySQL expects the password initialized in those system files. If login fails after recreating, ensure you use the exact password originally initialized in that data directory.
6. Stateful Persistence: MySQL with Docker Volumes
Using managed Docker volumes for databases eliminates path-management overhead while ensuring production-grade data persistence.
# 1. Create a dedicated volume for database data
docker volume create db_data
# 2. Run MySQL container using the named volume
docker run -d \
--name new_db \
-e MYSQL_ROOT_PASSWORD=admin \
-v db_data:/var/lib/mysql \
mysql
# 3. Access container, create tables, and insert records
docker exec -it new_db mysql -u root -padmin
CREATE DATABASE testDB2;
USE testDB2;
CREATE TABLE persons (person_id INT, last_name VARCHAR(50), first_name VARCHAR(50));
INSERT INTO persons VALUES (1, 'Gore', 'Shubham');
exit;
# 4. Destroy the container
docker rm -f new_db
# 5. Recreate container with the same volume
docker run -d \
--name db_wall \
-e MYSQL_ROOT_PASSWORD=admin \
-v db_data:/var/lib/mysql \
mysql
# 6. Verify data persistence
docker exec -it db_wall mysql -u root -padmin
USE testDB2;
SELECT * FROM persons;
# Output: Returns the previously inserted row successfully!
7. Comparative Analysis: Bind Mounts vs. Docker Volumes
8. Summary and Key Takeaways
- Stateless by Default: Containers are stateless; deleting a container destroys all internal filesystem changes unless persistence mechanisms are attached.
- Bind Mounts (-v) link an explicit host machine directory to a container directory, ideal for local development and live file syncing.
- Docker Volumes are managed by Docker (
/var/lib/docker/volumes/), offering superior portability, security, and abstraction over host paths. - Shared Storage: Multiple containers can mount the exact same volume or bind mount simultaneously, enabling real-time data sharing across services.
- Database Persistence: Stateful applications like MySQL store data in specific internal paths (e.g.,
/var/lib/mysql); mounting these paths to volumes ensures zero data loss during container failures or deployments. - Troubleshooting Active Mounts: Docker volumes in use by running containers cannot be deleted directly (
docker volume rm); containers must be stopped and removed first.