Welcome to my tutorials on how to leverage Docker to deploy your application! This is meant as much to help me learn, so it doubles as a “what worked for me” series.
In the previous lesson, we created an image by injecting an application into the image without using packages and set the image up to use environment variables as a way to change settings in the container when the container is created.
This lesson, we we cover how to use volumes to keep data even after deleting a container.
Normally, a Docker container is ephemeral: the container and all its contents are intended to be thrown away, and application developers are expected to design their docker applications for this. So, if someone wants to upgrade, they destroy the old container and install the new version. But then how does this benefit an application like MySQL?
The solution is in the use of volumes. A volume is a chunk of storage that is “attached” to a container. It can be a file or folder on the Docker host that is mounted to the inside of the container, allowing the container to store data on “the outside”. Another option, however, is the creation of a named image layer (also called a volume) that replaces a folder inside of the container. So for this lesson, we are going to create a volume that will store our MySQL data, create a container that runs an older version of MySQL, add some data, then delete and upgrade to a new version without losing our data.
We can easily create a volume with docker volume create
:
docker volume create --name mysql
Now we can create our container. We are going to use the docker run
command line to get this running borrowing some directions from https://registry.hub.docker.com/_/mysql:
docker run --name mysql_80 -d -v "mysql:/var/lib/mysql" -e "MYSQL_ROOT_PASSWORD:example" -p "3306:3306" mysql:8.0 --default-authentication-plugin=mysql_native_password
The new -v
option specifies a volume to use and where to mount it. You can specify an absolute path or, if using a *nix system, a relative path for the volume, and it will attach the Docker host’s directory or file and put it at the location you specify after the colon inside the container. Volumes that are not a path (does not start with “/” or “./”, or is not a valid full Windows path) are expected to be volumes that were created in docker, and either need to be the full volume ID or the name assigned while creating the volume. You may also make the volume inside the container read-only by adding “:ro” to the end of the volume argument. You may have more than one volume attached to a container, if desired.
The parameters after the mysql:8.0 image name are command arguments that are passed into the image. One Dockerfile statement we have not seen yet is ENTRYPOINT, which defines the specific program to run and then the CMD statement supplies arguments to the program indicated by ENTRYPOINT. If you add parameters after the image name, these replace the contents of the CMD statement from the image’s Dockerfile. In this case, the new command values direct MySQL to use it’s internal account management instead of the default the image was set to.
If you have your myPhpAdmin container running, you can now visit http://localhost:8080 and login as user “root”, password “example”. If not, you can build the image and run it following the directions from the last lesson.
Now that we have a MySQL server running, let’s add some data so that we can clearly see that the data is stored in the volume we created instead of in the container. We’re going to connect to a shell in the container and create a new database and table and store a row of data.
docker exec -it mysql_80 /bin/bash mysql -uroot -pexample create database vehicle; create table vehicle.cars (model varchar(16)); insert into vehicle.cars values ("Pinto");
Before we leave the mysql command and the container shell, let’s confirm that our data is there.
select * from vehicle.cars;
If it says “Pinto”, then we’re set. Exit out of the mysql shell and the container shell. Next, stop and delete the MySQL container:
docker container stop mysql_80 docker container rm mysql_80
Now if we had not attached a volume to /var/lib/mysql, our precious Pinto would be lost. But we attached a volume there, and so our data should safely still be in that volume. So now let’s create a new container using the latest version of MySQL. Well, it happens to be the same version, but the data should still be there because of the volume, even though we have a new container:
docker run –name mysql_latest -v “mysql:/var/lib/mysql” -p “3306:3306” mysql –default-authentication-plugin=mysql_native_password
docker run --name mysql_latest -v "mysql:/var/lib/mysql" -p "3306:3306" mysql --default-authentication-plugin=mysql_native_password
Now you might have noticed that we do not include the environment variable for the root password. This is only needed when the database is being created. Since we have the database files already created in our volume, we should not need this anymore. To confirm, we are running this docker command without the -d
parameter, so the mysql server will run as long as the docker run command does. If we see in the last line “ready for connections” and not “needs MYSQL_ROOT_PASSWORD”, then we are in good shape. Hit Control-C to get your shell prompt back, then run the following to get into a container shell:
docker start mysql_latest docker exec -it mysql_latest /bin/bash
From here, we run our select query to see what’s inside:
echo "select * from vehicle.cars;"|mysql -uroot -pexample
And our Pinto should still be there, even though we deleted the original container and are in a new one. Volumes rock!
There are two imporant things to point out about volumes. First, it might be possible to share a volume between two containers at the same time, depending on the driver used to create the volume. The driver will specify the conditions for sharing the volume; some will permit read-write access to multiple containers at the same time, some only allow read-write to one container but read-only to others, and still some only allow access to one container at a time. Read up on the driver documentation to see what you can do with a volume.
Second, depending on the driver, your volume may be accessible from multiple docker hosts at the same time. In an enterprise environment, this allows you to “move” your container from one host to another, which is extremely helpful for resource allocation and management, as well as conducting server maintenance. However, even if the volume can be accessed from many hosts, the limitations mentioned above may still apply. For example, the volume might only be accessible by one container at a time, but since the volume is accessible from many hosts, you have your choice of host to run the container (and access the volume) on.
To summarize, we showed how use a volume with a container to persist data separate from the container that created and uses the data.
For the next lesson, we will learn how to use docker-compose
to define an application that spans multiple containers.