Docker Volumes Explained: A Step-by-Step Guide (With and Without Volumes)
If you've ever restarted a Docker container and watched all your data disappear, this post is for you. We're going to walk through why Docker volumes exist, what happens when you don't use them, and then do a full hands-on comparison step by step — with room for you to drop in your own screenshots as you follow along.
Table of Contents
What Problem Are We Solving?
By default, Docker containers are ephemeral. Any file a container writes lives inside the container's writable layer. The moment you remove that container (docker rm), all of that data is gone forever — including databases, uploaded files, logs, or configuration changes.
Docker Volumes solve this by storing data outside the container's lifecycle, on the host machine (managed by Docker), so it persists across container restarts, removals, and even recreation.

Part 1: Life Without Docker Volumes
Let's prove the problem exists before we fix it. We'll spin up a simple database container, write some data, delete the container, and watch it vanish.
Step 1.1 — Run a container without a volume
docker run -d --name mysql-no-volume -e MYSQL_ROOT_PASSWORD=root123 mysql:8.0
This starts a MySQL container. All of MySQL's data files (/var/lib/mysql) live inside the container's own filesystem layer — nowhere else.

Step 1.2 — Connect and create some data
docker exec -it mysql-no-volume mysql -uroot -proot123
Inside the MySQL shell:
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT, name VARCHAR(50));
INSERT INTO users VALUES (1, 'Alice');
SELECT * FROM users;
Exit the shell:
EXIT;
Step 1.3 — Stop and remove the container
docker stop mysql-no-volume
docker rm mysql-no-volume

Step 1.4 — Recreate the container and check for data
docker run -d --name mysql-no-volume -e MYSQL_ROOT_PASSWORD=root123 mysql:8.0
docker exec -it mysql-no-volume mysql -uroot -proot123 -e "SHOW DATABASES;"
Result: testdb is gone. All the data you created is permanently lost, because it lived inside the deleted container's writable layer.

This is the core pain point: containers are meant to be disposable, but your data usually isn't.
Part 2: Life With Docker Volumes
Now let's fix it using a named volume.
Step 2.1 — Create a named volume
docker volume create mysql-data

Verify it exists:
docker volume ls

Step 2.2 — Run a container using that volume
docker run -d --name mysql-with-volume \
-e MYSQL_ROOT_PASSWORD=root123 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
Here, -v mysql-data:/var/lib/mysql tells Docker: "Mount the volume mysql-data at path /var/lib/mysql inside the container." Now MySQL's data directory is backed by the volume, not the container's internal layer.

Step 2.3 — Create data again
docker exec -it mysql-with-volume mysql -uroot -proot123

CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT, name VARCHAR(50));
INSERT INTO users VALUES (1, 'Alice');
SELECT * FROM users;
EXIT;

Step 2.4 — Destroy the container (but not the volume)
docker stop mysql-with-volume
docker rm mysql-with-volume
Note: we removed the container, but the volume (mysql-data) still exists independently.
docker volume ls

Step 2.5 — Recreate the container with the same volume
docker run -d --name mysql-with-volume \
-e MYSQL_ROOT_PASSWORD=root123 \
-v mysql-data:/var/lib/mysql \
mysql:8.0
docker exec -it mysql-with-volume mysql -uroot -proot123 -e "SHOW DATABASES; SELECT * FROM testdb.users;"
Result: testdb and Alice's row are exactly where you left them.

Side-by-Side Comparison Table
Aspect | Without Volumes | With Volumes |
Data location | Inside container's writable layer | Managed by Docker, outside the container |
Data on container removal | Lost permanently | Persists |
Sharing data between containers | Not possible | Multiple containers can mount the same volume |
Backup / migration | Difficult (must export from inside container) | Easy (docker run --rm -v ... backup pattern, or copy the volume path) |
Performance (Linux) | Fine, but irrelevant since data isn't durable | Native filesystem performance, no overhead |
Managed by Docker CLI | No | Yes (docker volume create/ls/inspect/rm) |
Best for | Truly disposable/stateless containers (e.g., short scripts) | Databases, uploads, logs, configs, anything stateful |

Bind Mounts vs Named Volumes
While we're here, it's worth understanding the other common way to persist data — bind mounts.
docker run -d --name mysql-bind-mount \
-e MYSQL_ROOT_PASSWORD=root123 \
-v /home/user/mysql-data:/var/lib/mysql \
mysql:8.0
Named Volume | Bind Mount | |
Managed by | Docker | You (manual host path) |
Location | /var/lib/docker/volumes/... | Any path you choose on host |
Portability | High (works the same across machines) | Lower (depends on host path existing) |
Use case | Production, databases | Local development, sharing source code into a container |
Common Volume Commands Cheat Sheet
# Create a volume
docker volume create my-volume
# List all volumes
docker volume ls
# Inspect a volume (see its host path, driver, etc.)
docker volume inspect my-volume
# Remove a specific volume
docker volume rm my-volume
# Remove all unused (dangling) volumes
docker volume prune
# Mount a volume when running a container
docker run -v my-volume:/path/in/container my-image
Wrapping Up
The core lesson: containers should be disposable, data usually shouldn't be. Docker volumes decouple the two, letting you freely recreate, upgrade, or scale containers without ever touching the data they depend on.
A quick mental model to keep:
No volume → data lives and dies with the container.
Named volume → data outlives the container, managed by Docker.
Bind mount → data outlives the container, managed by you, tied to a specific host path.

$50
Product Title
Product Details goes here with the simple product description and more information can be seen by clicking the see more button. Product Details goes here with the simple product description and more information can be seen by clicking the see more button

$50
Product Title
Product Details goes here with the simple product description and more information can be seen by clicking the see more button. Product Details goes here with the simple product description and more information can be seen by clicking the see more button.

$50
Product Title
Product Details goes here with the simple product description and more information can be seen by clicking the see more button. Product Details goes here with the simple product description and more information can be seen by clicking the see more button.


Comments