Relational databases provide a structured approach for storing and organizing large volumes of data into tables consisting of rows for records, and columns for attributes. They use primary keys which are unique identifiers for records and foreign keys, to establish well defined relationships between tables.
These databases are widely used since they are reliable, consistent, and highly adaptable to various applications. They also provide complex database queries and operations capabilities using a structured query language (SQL). They are backed up with a strong and mature ecosystem; with extensive tooling and community support.
MySQL, PostgreSQL, MariaDB, Microsoft SQL Server, and Oracle databases are all examples of relational databases. You can run them within virtual machines or Docker containers. This article, on the other hand, will include the steps to run MySQL, which is one of the widely used relational databases in a Docker container.
Why run a Relational Database with Docker
You might be wondering why one would run a complex system like a relational database in a Docker container in the first place, well hang on. There is no doubt that containers are designed primarily for stateless applications. That is to say, containers are ephemeral in nature and without proper configuration, the operational status of a container could be disrupted, resulting in data loss.
Databases, on the other hand, are stateful and require persistent storage to run optimally, especially in times of restarts and even crashes. Yes, Docker volumes are designed to persist data beyond the lifecycle of a container; however, running a relational database is fine for development and testing purposes but is not recommended for production. Instead, for production environments you should run them on standalone servers, managed database services like AWS RDS or Azure SQL database, or store data on an external volume like cloud storage or block storage (AWS EBS, Azure managed Disks, etc).
Here are some of the reasons why you might need to run a relational database with Docker in detail:
- Isolation and reproducibility: Docker containers provide you with the ability to run applications in a completely isolated environment; to test new database features, configurations, or updates like security parameters, user roles, authentication, and many more.
- Microservice Decoupling: To implement a microservice architecture whereby you can ensure data isolation to one service’s database without affecting the database of a different service.
- Try and Error: To experiment with different database configurations on the same machine using multiple database instances. You can test different database engines or different versions of the same engine.
In this article, I will show you how to get MySQL up and running, and show you how to set it up and use MySQL On Docker for your non-production environments.
Prerequisites
To follow along, you are required to have Docker engine installed on your machine. This tutorial uses the latest versions of Docker (28.0.1) on an Ubuntu 24.04 LTS server from when this piece was written. There may be subtle differences as time passes, and if you are using a different operating system.
Pulling the Official MySQL Docker Image
Whether it’s a relational or non-relational database, or something else entirely, you need to pull a base image if you would like to run anything with Docker. In the case of a relational database like MySQL, the official Docker image from DockerHub is maintained by the MySQL team. It is pre-configured with all dependencies to run MySQL effectively. It is also properly documented to help you quickly install and make configurations for MySQL on your host system.
Execute the following command to pull the official MySQL docker image from DockerHub:
1 |
docker pull mysql |
This should pull the latest version from DockerHub. Once the MySQL image has been pulled successfully, you are expected to have the following output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Using default tag: latest latest: Pulling from library/mysql 43759093d4f6: Pull complete d255dceb9ed5: Pull complete 23d22e42ea50: Pull complete 431b106548a3: Pull complete 2be0d473cadf: Pull complete f56a22f949f9: Pull complete 277ab5f6ddde: Pull complete df1ba1ac457a: Pull complete cc9646b08259: Pull complete 893b018337e2: Pull complete Digest: sha256:146682692a3aa409eae7b7dc6a30f637c6cb49b6ca901c2cd160becc81127d3b Status: Downloaded newer image for mysql:latest docker.io/library/mysql:latest |
Confirm that the MySQL image is available on your machine using the following command:
1 |
docker images |
You are good to go if it is listed among the Docker images available on your machine:
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE mysql latest 5568fddd4f66 6 weeks ago 797MB |
Creating a Docker Network
Networks in Docker represent a communication environment that allows containers to communicate and interact with each other in a secure manner. Regardless of what kind of image or application you are running with Docker, you should always consider creating a Docker network to ensure isolated communication between your containers.
Docker supports seven network drivers by default, you can find them on the official Docker documentation. If other network drivers are not set, the bridge network driver is the default. Additionally, a Docker network is automatically created when you run a Docker image as a container. In our case, we will create a custom docker network using the bridge network driver and give it a generic name mysql-net with the following command:
1 |
docker network create -d bridge mysql-net |
Confirm that mysql-net
is listed amongst the list of Docker networks on your machine:
1 |
docker network ls |
You should see it listed in the list like so:
1 2 |
NETWORK ID NAME DRIVER SCOPE 29931b56ee5a mysql-net bridge local |
Run the MySQL image as a Docker container, connect to the mysql-net
network and initialize the MySQL database with a root password. If you are going to use this instance for any work, use a proper password:
1 |
docker run -d --network=mysql-net -e MYSQL_ROOT_PASSWORD=superadminuser mysql |
Take note, you can also initialize the MySQL database with two more authentication-related environment variables. For example:
MYSQL_ALLOW_EMPTY_PASSWORD
: This environment variable allows the MySQL database to start without a root password; which is not proper for production environments.MYSQL_RANDOM_ROOT_PASSWORD
: This environment variable allows the MySQL database to start with a random root password and then print it in the logs.
For our case, our current setup will suffice. Verify that the MySQL database is up and running using the command below:
1 |
docker ps |
You should have the output similar to the following:
1 2 |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9c8b1f4a9b57 mysql "docker-entrypoint.s…" 9 seconds ago Up 8 seconds 3306/tcp, 33060/tcp blissful_noether |
Dates, times and the CONTAINER ID
value will be different, but the output should look very similar.
Checking for Data Persistence
Again, containers are stateless, a database that runs in a container risks losing all its data if the container is deleted whether on purpose or unexpectedly. Therefore, it is mandatory to use data persistence mechanisms to ensure that data is retained even if the container stops or is removed. In this case, you’ll consider common persistence mechanisms like Docker volumes or bind mounts.
To begin, let’s confirm data is not persistent when a container is deleted. Go into the MySQL container using the following command, replacing <container-id>
with your MySQL container ID:
1 |
docker exec -it <container-id> mysql -uroot -p |
For example, in my case it would be:
1 |
docker exec -it 9c8b1f4a9b57 mysql -uroot -p |
Input your password and you should get a prompt to enter MySQL commands. So to start, we will create a database named: book_hub
:
1 |
CREATE DATABASE book_hub; |
You should see messages after most commands. Like after this one you will see “Query OK, 1 row affected” for example. Next, select the book_hub
database:
1 |
USE book_hub; |
Create a table Publishers
in the book_hub
database:
1 |
CREATE TABLE Publisher(pubID varchar(255) not NULL, bname text(50) not NULL,city varchar(255) not NULL, country text(50) not NULL, PRIMARY KEY(pubID)); |
Next, add some data into the Publishers
table, the following values:
1 2 3 4 5 6 7 8 |
INSERT INTO Publisher (pubID, bname, city, country) VALUES ('p22', 'Random House', 'Las Vegas', 'United States'), ('p10', 'Dell Books', 'Colorado', 'United States'), ('p078', 'Pen Books', 'London', 'United Kingdom'), ('p279', 'Signet Books', 'Aberdeen', 'United Kingdom'), ('p12', 'Macmillan', 'London', 'United Kingdom'), ('p29', 'Madhavan', 'Aberdeen', 'United Kingdom'); |
Confirm that all values have been inserted (in addition to the message you will have received):
1 |
SELECT * FROM Publisher; |
You should have the following output:
1 2 3 4 5 6 7 8 9 10 |
+-------+--------------+-----------+----------------+ | pubID | bname | city | country | +-------+--------------+-----------+----------------+ | p078 | Pen Books | London | United Kingdom | | p10 | Dell Books | Colorado | United States | | p12 | Macmillan | London | United Kingdom | | p22 | Random House | Las Vegas | United States | | p279 | Signet Books | Aberdeen | United Kingdom | | p29 | Madhavan | Aberdeen | United Kingdom | +-------+--------------+-----------+----------------+ |
Exit out of the MySQL container and exec back into the MySQL container:
1 2 3 |
exit docker exec -it <container-id> mysql -uroot -p |
The aim here is to check if the data is still available even after exiting out of the MySQL container.
Now select the book_hub
database and view the publishers table with the following commands:
1 2 3 |
USE book_hub; SELECT * FROM Publisher; |
You should see the contents of the Publisher table outputted to you which is expected. This simply means that exiting out of the MySQL container doesn’t incur data loss. To simulate data loss, you’ll need to remove the MySQL container and run a new one.
Stop and remove the MySQL container:
1 |
docker rm <container-id> -f |
Note: While containers do not lose data when you exit it, or even if you restart the container (which you can do with the Docker start and stop commands), being able to destroy and recreate the container makes it easier to test new features or updates without affecting the data you have stored. Some care may need to be taken to use a compatible version of MySQL (or any tool you are using), and it may be best to make a copy of your data before attaching that new container.
Next, Initialize a new MySQL instance:
1 |
docker run -d --network=mysql-net -e MYSQL_ROOT_PASSWORD=superadminuser mysql |
Then connect to it by finding the container Id:
1 |
docker ps |
Then connecting to it by using the container id.:
1 |
docker exec -it <container-id> mysql -uroot -p |
Input your password and attempt to use the book_hub database:
1 |
USE book_hub; |
You should see an error message similar to the one below:
ERROR 1049 (42000): Unknown database ‘book_hub’
This is expected because the book_hub database and its data were stored inside the previous container. Since we removed that container, all its data was lost.
Configuring Data Persistence with Docker Volumes
Docker volumes allow you to store data outside of Docker containers. With Docker volumes data can be maintained across container lifecycles so you don’t have to worry about data loss in time of container crashes or disruptions.
To begin, you must run the MySQL container with a volume. You can create a volume separately and then attach it to the MySQL container, or you create one on the go while running the MySQL container. Use the following command to run the MySQL container with a volume called mysql-data:
1 2 3 4 |
docker run -d --network=mysql-net \ -e MYSQL_ROOT_PASSWORD=superadminuser \ -v mysql-data:/var/lib/mysql \ mysql |
Confirm the MySQL container is running and get its container ID:
1 |
docker ps |
Go into the MySQL container:
1 |
docker exec -it <container-id> mysql -uroot -p |
Create a database with the name book_hub and select it as your database. Follow the same process as before using these commands:
1 |
CREATE DATABASE book_hub; |
1 |
USE book_hub; |
1 2 3 4 5 6 7 |
CREATE TABLE Author ( authorID VARCHAR(255) NOT NULL, bname TEXT NOT NULL, city VARCHAR(255) NOT NULL, country TEXT NOT NULL, PRIMARY KEY (authorID) ); |
1 2 3 4 5 6 7 |
INSERT INTO Author (authorID, bname, city, country) VALUES ('A21', 'Grisham', 'New York', 'United States'), ('A01', 'Cook', 'London', 'United States'), ('A27', 'Hailey', 'Manchester', 'United Kingdom'), ('A301', 'Segal', 'Houton', 'United Kingdom'), ('A478', 'Forsyth', 'New York', 'United Kingdom'); |
Now, view the Author data;
1 |
SELECT * FROM Author; |
And just like earlier in the article, you should see the following output:
1 2 3 4 5 6 7 8 9 |
+----------+---------+------------+----------------+ | authorID | bname | city | country | +----------+---------+------------+----------------+ | A01 | Cook | London | United States | | A21 | Grisham | New York | United States | | A27 | Hailey | Manchester | United Kingdom | | A301 | Segal | Houston | United Kingdom | | A478 | Forsyth | New York | United Kingdom | +----------+---------+------------+----------------+ |
Exit the MySQL container by typing exit and proceed to delete the MySQL container using the following command:
1 |
docker rm <container_id> -f |
Confirm that the Docker volume mysql-data
still exists:
1 |
docker volume ls |
Because the MySQL container was removed doesn’t mean the Docker volume is also, you should see it listed among other Docker volumes present on your machine:
1 2 3 |
DRIVER VOLUME NAME local 5c2849d0dbfca9187b96438ef37c613b384978d5d18e3fc35c6b3a462f3d0025 local mysql-data |
Now spin up a new container and attach the volume (mysql-data) to it:
1 2 3 4 |
docker run -d --network=mysql-net \ -e MYSQL_ROOT_PASSWORD=superadminuser \ -v mysql-data:/var/lib/mysql \ mysql |
Connect to the MySQL container (again finding the container id with docker ps
):
1 |
docker exec -it <container-id> mysql -uroot -p |
Confirm that the book_hub
database exists by attempting to select it:
1 |
USE book_hub; |
If you have the following output, you have successfully persisted data with Docker volumes.
This shows that the database exists:
1 |
Database changed |
This is also true for all tables in the book_hub
database, for example, view the Author
table:
1 |
SELECT * FROM Author; |
Should return:
1 2 3 4 5 6 7 8 9 |
+----------+---------+------------+----------------+ | authorID | bname | city | country | +----------+---------+------------+----------------+ | A01 | Cook | London | United States | | A21 | Grisham | New York | United States | | A27 | Hailey | Manchester | United Kingdom | | A301 | Segal | Houtons | United Kingdom | | A478 | Forsyth | New York | United Kingdom | +----------+---------+------------+----------------+ |
So now we have part of the picture.
Configuring Data Persistence with Bind Mounts
Bind mounts can also ensure data persistence; especially when you want your container to have direct access to host files. While slower due to direct filesystem access, you can persist data as we have done with Docker volumes.
To use bind mounts, you must first create the directory on your host machine that you want to mount into the container. If the directory already exists you’ll need to mount the host on the go while running the MySQL container. Keep in mind that bind mounts use your host filesystem and are not managed by Docker.
Create a directory mysql-data
on the host with the following command:
1 |
sudo mkdir -p /documents/mysql-data |
Run the MySQL container and mount the host directory inside the MySQL container:
1 2 3 |
docker run -d --network=mysql-net -e MYSQL_ROOT_PASSWORD=superadminuser \ -v /home/user/documents/mysql-data:/var/lib/mysql \ Mysql |
Check that the MySQL container is running:
1 |
docker ps |
Go inside the MySQL container and create a database :
1 |
docker exec -it <container-id> mysql -uroot -p |
Enter the root password and then execute the following commands sequentially:
1 2 3 |
CREATE DATABASE testdb; SHOW DATABASES; EXIT; |
Remove the MySQL container:
1 |
docker rm <container-id> -f |
Rerun
the MySQL container:
1 2 3 |
docker run -d --network=mysql-net -e MYSQL_ROOT_PASSWORD=superadminuser \ -v /home/user/documents/mysql-data:/var/lib/mysql \ Mysql |
Check if the testdb
still exists by running the below command:
1 |
docker exec -it <container-id> mysql -uroot -p |
Then execute:
1 |
SHOW DATABASES; |
You should see the testdb
database listed among all available databases:
1 |
mysql> SHOW DATABASES; |
Then you should see:
1 2 3 4 5 6 7 8 9 10 |
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | testdb | +--------------------+ 5 rows in set (0.02 sec) |
This shows that data has persisted, so you can now drop and recreate the container, referencing the Bind Mount, and the data you expect will be there.
Conclusion
You can run any application in a containerized environment with Docker, including a relational database like MySQL, and have an understanding how to keep data persisted, even after a reboot.
In this article, you have seen how to run a MySQL database in Docker, configure data persistence using Docker volumes and bind mounts and most of all, set up replication, by deploying master and replica containers on the same Docker network.
Load comments