DEV Community

Cover image for Run a MySQL container with persistent storage using Docker volumes
Deborah Emeni
Deborah Emeni

Posted on

Run a MySQL container with persistent storage using Docker volumes

What you'll learn

By the end of this section, you'll:

  1. Understand what happens to data inside a container and why it does not stay once the container is removed.
  2. Learn what Docker volumes are and how they help you keep data safe even when containers stop or get recreated.
  3. See the difference between bind mounts and named volumes, and know which one to use in simple development situations.
  4. Learn how a container reads and writes data to a location outside its own filesystem using volumes.
  5. Complete a hands-on project where you run a MySQL container with persistent storage so your database stays intact across restarts.

We’ll begin by looking at how container storage works and why volumes are needed.


Why do containers lose data?

Before we start working with volumes, it helps to understand how container storage works.

Every container has its own isolated filesystem. It's like a temporary workspace that only that container can see.

When you start a container, Docker creates a thin writable layer on top of the image. Any changes the container makes, such as logs, uploaded files, and database entries, go into this writable layer.

The key point is that this layer is not designed to stay around forever. Once you stop or remove the container, Docker wipes the writable layer along with anything stored inside it. The image remains, but your data does not.

A quick example to visualize this

Let's say you run a MySQL container, create a database table, and insert some rows.

As long as the container is running, everything looks fine. But if you delete that container and start a new one using the same image, MySQL starts up as if it is a fresh installation.

The data you added earlier is gone because it lived only inside the old container’s temporary filesystem.

This is why we need a way to store data outside the container’s lifecycle. And that is exactly what Docker volumes provide.


What are Docker volumes?

Docker volumes give containers a place to store data that lives beyond the container itself.

So, instead of writing everything to the container’s short-lived filesystem, a volume provides a separate storage location that Docker manages for you.

A better way to understand it is this: the container has its own internal workspace, but a volume sits outside that workspace. When you attach a volume to a container, the container can read from it and write to it as if it were part of its own filesystem, even though the data is actually stored elsewhere.

docker-volumes

Why are Docker volumes used?

Docker handles all the behind-the-scenes work. It creates the volume, keeps track of where it lives on your machine, and makes sure it is available whenever a container needs it. You do not have to manage the actual folder path or worry about accidentally deleting it.

Volumes are used because writing data directly inside a container is not reliable. If the container is removed, everything inside it disappears.

By using a volume, the data stays safe, and you can start fresh containers that still have access to the same information. This is very helpful for databases, logs, or anything you expect to keep long-term.


Bind mounts vs named volumes - what’s the difference?

When you attach external storage to a container, you’re choosing how the container should access data that lives outside its own filesystem. The two ways to do this are bind mounts and named volumes, and each one fits a different situation.

Bind mounts

A bind mount directly links a folder on your host machine into the container, so both sides see the same files immediately.

Named volumes

A named volume is storage created and managed by Docker, giving your container a stable place to store data without relying on your host’s folder structure.

See this table below that summarizes this comparison

Let's look at a simple way to compare both approaches so you can decide when to use each one:

Feature Bind mounts Named volumes
Where data lives A folder on your host machine Docker-managed storage
Best for Local development, hot-reloading Databases, production workloads
Portability Low (path depends on the host) High (Docker manages it)
Reliability Tied to host system setup More stable and predictable
Visibility Easy to browse directly on your machine Harder to inspect manually
Permissions Can be tricky across OSes Handled by Docker

How do containers store and retrieve data using volumes?

Before we run our MySQL example, I'll explain what happens when a container uses a volume.

Containers normally write data into their own internal filesystem, but when you attach a volume, you’re telling the container:

“Store this data somewhere safer, outside your temporary filesystem.”

Every container keeps its data in specific directories. For MySQL, that directory is:

/var/lib/mysql
Enter fullscreen mode Exit fullscreen mode

This is where MySQL writes tables, logs, metadata, and all database files.

If this path stays inside the container, the data disappears when the container is removed. But if we mount a volume to this path, something different happens.

What exactly happens when a volume is mounted?

So, for example, when you attach a volume to /var/lib/mysql:

  • Docker gives the container a storage location that lives outside the container.
  • Anything MySQL writes goes into the volume instead of the container’s own filesystem.
  • If you delete the container and start a new one with the same volume, the new container can see all the old files immediately.

This is why volumes are used for databases. They let the container come and go, while the data stays in place.

What does the data flow look like, in simple terms?

  1. The container starts.
  2. Docker mounts the volume into the container at a specific path.
  3. The application (like MySQL) reads/writes files as if the folder was part of its normal filesystem.
  4. Docker handles the actual storage behind the scenes.

The key idea is this: the container thinks it’s writing to its own filesystem, but the data is safely stored in the volume instead.


Hands-on project: Run a MySQL container with persistent storage using Docker volumes

Now that you understand what volumes are and how containers use them, let’s put it all together by running a MySQL container whose data survives restarts and rebuilds.

We’ll go step by step so you not only run the container but also understand the importance of each step.

Before you begin

To follow along with the hands-on MySQL project, make sure you have the following set up:

  1. Docker installed and running
    You should have Docker Desktop (macOS/Windows) or Docker Engine (Linux) installed.
    If you’ve completed the earlier parts of this series, your setup should already be ready. (If not, follow this guide to make sure Docker is installed and running.)

  2. Basic familiarity with running containers
    You should know how to use commands like docker run, docker stop, and docker exec.
    We’ll build on these, but you don’t need advanced knowledge.

  3. A terminal or command prompt open
    All the commands in this section will be run from your terminal.

  4. Port 3306 available on your machine
    MySQL uses port 3306. If another MySQL server is already running locally, you may need to stop it.

  5. At least a few hundred MB of free disk space
    The MySQL image and its data files need some space to run smoothly.

Once you have these ready, you can safely move on to the first step.

Step 1: Pull the MySQL image

Open your terminal or command prompt. This can be:

  • Terminal on macOS or Linux
  • Command Prompt or PowerShell on Windows
  • Or the built-in terminal inside tools like VS Code

Make sure Docker Desktop is running in the background.
You can check by running:

docker --version
Enter fullscreen mode Exit fullscreen mode

If Docker responds with a version number, you’re good to continue.

docker version output

Now, let’s download the official MySQL image from Docker Hub. This image contains everything needed to run a MySQL server inside a container.

Run:

docker pull mysql:latest
Enter fullscreen mode Exit fullscreen mode

The first time you pull it, Docker will download all the layers it needs.

If you already have it, Docker will simply check for updates and use the cached version.

docker pull mysql output

When this completes, you now have the MySQL image available locally and ready to run in a container.

Step 2: Create a named Docker volume

Before we run MySQL, we need a place for it to store its data. Instead of letting it write into the container’s temporary filesystem, we’ll give it a named Docker volume. This ensures the data stays safe even if the container is deleted.

In your terminal, run:

docker volume create mysql_data
Enter fullscreen mode Exit fullscreen mode

This creates a volume named mysql_data.

You won’t see a folder appear on your machine, because Docker manages the volume internally. The important thing is that the volume now exists, and we can attach it to the MySQL container in the next step.

Why create the volume ahead of time instead of letting Docker create it automatically?

Because doing it explicitly helps you see the whole flow: you create the storage location, and MySQL simply uses it.

This makes the idea of persistent storage much clearer when you start working with real databases and production workloads.

You can confirm that the volume was created by running:

docker volume ls
Enter fullscreen mode Exit fullscreen mode

Look for mysql_data in the list, if it’s there, you’re ready to move on.

Step 3: Run a MySQL container using the volume

Now that the volume is ready, the next step is to run a MySQL container and attach that volume to MySQL’s data directory.

This is the point where the container and the volume start working together.

In your terminal, run:

docker run -d \
  --name mysql_container \
  -e MYSQL_ROOT_PASSWORD=my-secret-password \
  -v mysql_data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:latest
Enter fullscreen mode Exit fullscreen mode

This confirms the container has started successfully, seeing a long container ID means it’s running.

This single command does a lot, so here’s what each part means:

  • --name mysql_container
    Assigns a readable name to the container so it’s easy to start, stop, or access later.

  • -e MYSQL_ROOT_PASSWORD=my-secret-password
    Creates the root password for MySQL inside the container.
    (This password will be needed later when connecting to the database.)

  • -v mysql_data:/var/lib/mysql
    Mounts the volume created in the previous step into MySQL’s internal data directory.
    This is what makes the data persist outside the container.

  • -p 3306:3306
    Exposes MySQL on your machine so you can connect using tools or clients.

Once this container is running, every table, row, and record MySQL writes from now on will live inside the mysql_data volume instead of the container’s temporary filesystem.

That means even if we delete the container, the data remains.

If you’d like, you can run the next command to confirm that the container is up and running:

docker ps
Enter fullscreen mode Exit fullscreen mode

If mysql_container appears in the list, you’re ready for Step 4.

Step 4: Connect to MySQL inside the container

Now that MySQL is running inside Docker, let’s connect to it using the MySQL CLI built into the container.

Run this command from your terminal:

docker exec -it mysql_container mysql -u root -p
Enter fullscreen mode Exit fullscreen mode

This opens an interactive MySQL session inside the container.

Right after running the command, MySQL asks for the root password you set earlier. You should see a password prompt like this:

Example output showing the password prompt

Enter the password and press Enter.

If authentication works, you’ll be dropped into the MySQL shell and shown the MySQL welcome banner. A successful login looks like this:

Example of a successful login

Now you’re inside MySQL and can run SQL commands normally. Let’s create some data so we can verify persistence later. Run the following inside the MySQL prompt:

CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO users VALUES (1, 'Alice');
SELECT * FROM users;
Enter fullscreen mode Exit fullscreen mode

The final SELECT should return the row you inserted. Here’s what the output looks like when the row is returned:

This confirms two things:

  1. MySQL is running correctly inside your container.
  2. The database files are being written into the mysql_data volume.

When you’re done, exit the MySQL shell:

exit;
Enter fullscreen mode Exit fullscreen mode

The container will continue running in the background.

Step 5: Stop and remove the container

Now that we’ve added data to MySQL, let’s demonstrate what happens when the container itself is removed.

The goal here is to show that even if the container is removed, the data stored in the volume remains intact.

From your terminal, run:

docker stop mysql_container
docker rm mysql_container
Enter fullscreen mode Exit fullscreen mode

Here’s what each command does:

  • docker stop shuts the container down safely.
  • docker rm deletes the container entirely.

When you run those two commands, you should see output similar to this:

Even though the container is now gone, your mysql_data volume still exists (remains untouched), and it still contains every database file MySQL wrote earlier.

This sets us up perfectly for the next step, where we’ll start a new container and confirm that the data is still there.

Step 6: Start a new container using the same volume

Now that the previous container has been removed, let’s start a brand-new MySQL container and attach the same volume (mysql_data).

This is where you’ll see Docker volumes doing exactly what they’re designed for.

Run the container:

docker run -d \
  --name mysql_container \
  -e MYSQL_ROOT_PASSWORD=my-secret-password \
  -v mysql_data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:latest
Enter fullscreen mode Exit fullscreen mode

Here's what it looks like when the container starts successfully:

Screenshot of docker run command starting a new MySQL container

This is the same command you ran earlier. The container is new, but the volume still contains all of MySQL’s data files from before.

Connect back into MySQL

Now that the container is running again, open a MySQL shell inside it:

docker exec -it mysql_container mysql -u root -p
Enter fullscreen mode Exit fullscreen mode

Docker will prompt you for the root password:

Enter the same password you set earlier.

Verify that your data is still there

Once inside the MySQL shell, run the following SQL commands:

SHOW DATABASES;
USE testdb;
SELECT * FROM users;
Enter fullscreen mode Exit fullscreen mode

You should now see the same database and table you created earlier, including the “Alice” row:

Your new container didn’t have to recreate anything. It simply reused the data stored in the volume. This is the entire point of Docker volumes: containers may be temporary, but your data is not.

Step 7: Inspect the volume (optional)

If you want to see where Docker is storing your MySQL data on your machine, you can inspect the volume directly:

docker volume inspect mysql_data
Enter fullscreen mode Exit fullscreen mode

This command prints the full details of the mysql_data volume, including the path on your system where Docker keeps the files.

docker volume inspect output for mysql_data

Pay attention to the "Mountpoint" field in the output. That path is where Docker stores all of MySQL’s internal data files, and it remains available even after containers are removed. This confirms that the volume persists independently of any container.


So what you just learned (and why it’s useful)

In this part of the series, I walked you through how Docker volumes keep your data safe even when containers are stopped or removed.

You went through the process step by step:

  • You created a named volume for MySQL’s data.
  • You ran a MySQL container and attached that volume to its data directory.
  • You created a database, table, and record inside MySQL.
  • You removed the container and started a new one using the same volume.
  • You confirmed that the data was still there.

By doing this, you now know how to:

  • Keep important data outside a container’s temporary filesystem.
  • Reuse the same data across multiple containers.
  • Inspect a volume to see where Docker stores it.
  • Prevent data loss when containers are rebuilt during development.

This understanding becomes very practical once you start working with databases, background workers, message queues, or any service that needs to persist data.

In the next part of the series, I’ll show you how to manage everything with Docker Compose, so you can start multiple containers with a single command.

Top comments (0)