Deploying MySQL to get started using a Docker Compose file

; Date: Mon Mar 08 2021

Tags: Docker »»»» Docker Compose

While the docker run CLI command is powerful, Docker Compose files let us succinctly describe a whole system of Docker containers, virtual networks, and file systems, in one easy to read file. This is a powerful tool with which you can easily describe deployment of a full application stack. Compose files support not only deployment to the local Docker host, but to Docker Swarm clusters, and now to AWS ECS and Azure ACI cloud platforms.

For a long time, Docker Compose has been a command added to the Docker environment but not well integrated. It was originally developed outside the Docker team, but for several years has been distributed alongside the core Docker products, and has been bundled with the Docker Desktop products.

Its core feature is the Compose file which gave us a concise format to describe Docker deployments. In one file we can describe the deployment parameters of multiple Docker Services (a.k.a. Containers) as well as other resources such as virtual networks, volumes, and secrets. It means we can describe a whole system using one easy-to-understand file, and we can bring that whole system up using one command.

This file format started out life in the docker-compose command. With that command, the Compose file can deploy services to the local Docker host, or to a remote Docker host if you're using the Docker Context feature. This file is also supported with Docker Swarm by using the docker stack deploy command. More recently, the Docker Team integrated a new command, docker compose (note - no -) which lets us use the Compose file with cloud platforms like AWS ECS and Azure ACI.

In short, the Docker team is positioning the Compose file as a universal format for describing Docker service deployment.

A recent change is the development of a formal specification for the Compose file. The Docker team has described a vision that, because of the Specification, other tool makers will now be able to implement Compose functionality.

To give a little taste:

version: '3.8'

services:
    whoami:
        image: containous/whoami
        ports: 
            - "80:80"

This is a simple Compose file that can be used, unchanged, for deployment to any of the platforms just named. It uses the YAML format giving a lot of flexibility. The services tag contains the description of one or more Services, as the name implies. Each service is built using a Docker Image, and various parameters. The service description can also include environment variables, deployment parameters, and more. Remember that a Container is a Docker Image that is instantiated with specific configuration attributes. Hence, a Container and a Service is largely the same thing.

The containous/whoami container is popular as an example since it is a web service that prints a lot of useful information. This particular example exposes the whoami service on port 80.

That Compose file is equivalent to:

$ docker run --name whoami -p "80:80" containous/whoami

We can use either docker run or Compose files to launch Docker services. Both expose the same Docker capabilities, meaning that there is much similarity between the two.

Deploying the whoami container using Compose

To familiarize ourselves with docker-compose let's run the Compose file shown above. Create a new directory, and create a file named docker-compose.yml to contain the Compose file.

$ mkdir compose
$ cd compose
$ vi docker-compose.yml
... enter text
$ docker-compose up
Creating network "compose_default" with the default driver
Pulling whoami (containous/whoami:)...
latest: Pulling from containous/whoami
29015087d73b: Pull complete
0109a00d13bc: Pull complete
d3caffff64d8: Pull complete
Digest: sha256:7d6a3c8f91470a23ef380320609ee6e69ac68d20bc804f3a1c6065fb56cfa34e
Status: Downloaded newer image for containous/whoami:latest
Creating compose_whoami_1 ... done
Attaching to compose_whoami_1
whoami_1  | Starting up on port 80

Because the whoami image did not exist locally, it was downloaded from Docker Hub. Because we ran the up command, it will attempt to launch the service(s) described in the Compose file, and in this case it is successful. The Attaching to message means that the standard output from the service(s) will be shown on the terminal. The last line is a message from the whoami container saying it started on port 80.

To view the application visit http://localhost in your browser.

To view the status run this command in another window:

$ docker-compose ps
      Name         Command   State         Ports       
-------------------------------------------------------
compose_whoami_1   /whoami   Up      0.0.0.0:80->80/tcp

This is similar to the docker ps command, but a little bit different.

To bring the service down, run this command:

$ docker-compose down
Stopping compose_whoami_1 ... done
Removing compose_whoami_1 ... done
Removing network compose_default

Over in the original command window, the docker-compose command will exit.

Deploying MySQL using Docker Compose

That was a useful introduction to Compose files, but not very practical. To do something practical let's launch a MySQL service using a Compose file. Specifically, let's replicate the configuration used earlier when deploying Wordpress and PHPMyAdmin - Set up PHPMyAdmin and Wordpress Docker containers using existing MySQL

Remember in that case we used this command:

$ docker run --name mysql \
    --env MYSQL_RANDOM_ROOT_PASSWORD=true \
    --env MYSQL_USER=wpuser \
    --env MYSQL_PASSWORD=w0rdw0rd \
    --env MYSQL_DATABASE=wpdb \
    --volume `pwd`/mysql-data:/var/lib/mysql \
    --network wpnet \
    mysql/mysql-server:5.7

With that database instance we went on to deploy PHPMyAdmin and Wordpress.

Rename the existing Compose file to docker-compose-whoami.yml and create a new docker-compose.yml containing this:

version: '3.8'

services:

    mysql:
        image: "mysql/mysql-server:8.0.21"
        container_name: mysql
        command: [ "mysqld",
                    "--character-set-server=utf8mb4",
                    "--collation-server=utf8mb4_unicode_ci",
                    "--bind-address=0.0.0.0",
                    "--default_authentication_plugin=mysql_native_password" ]
        # ports:
        #     - "3306:3306"
        networks:
            - wpnet
        volumes:
            - type: bind
              source: ./database
              target: /var/lib/mysql
        restart: always
        environment:
           MYSQL_ROOT_PASSWORD: "r00tr00t"
           # MYSQL_ROOT_HOST: "%"
           MYSQL_USER: wpuser
           MYSQL_PASSWORD: w0rdw0rd
           MYSQL_DATABASE: wpdb

networks:
    wpnet:
        driver: bridge

Notice how all the elements of the docker run command are present in this Compose file. It's expressed a little differently, and we've moved to a later MySQL version, but everything is there.

The service name is mysql as is the container_name parameter. This will ensure a known container name, which we can use for accessing the database from other containers.

Using Wordpress with MySQL version 8 required specific server configuration parameters. Those parameters are specified in the command attribute. This attribute lets us override the CMD instruction in the Dockerfile, changing the command used to launch the MySQL service. While we could have supplied a custom my.cnf configuration file, the MySQL server lets us configure any setting using command-line options as shown here.

The networks attribute lets us attach the service to one or more Docker networks. In this case we've defined wpnet which will serve to connect services required to run Wordpress. At the bottom there is a top-level networks tag with which we define this network.

The next attribute, volumes, is the same as the --volumes option on the docker run command. The syntax here is a little different, and gives us more flexibility in defining the parameters. In this case we're simply mounting a host directory into the container as /var/lib/mysql in order to persist the data directory outside the container.

The restart attribute defines the policy to use if the container crashes. We're asking Docker to always restart the container.

The last, environment, is where we define configuration settings. In this case we're defining the database name, user name, and password.

Launching and exploring MySQL using Docker Compose

Earlier to launch services in a Compose file we used docker-compose up, so let's to it a little different.

$ mkdir database
$ docker-compose up -d
Creating mysql ... done

The -d flag is short for --detach which means the server will be brought up in the background.

The mkdir command is required to avoid this error: ERROR: for mysql Cannot create container for service mysql: invalid mount config for type "bind": bind source path does not exist

To see the logging output, type this:

$ docker-compose logs -f
Attaching to mysql
mysql    | [Entrypoint] MySQL Docker Image 8.0.21-1.1.17
mysql    | [Entrypoint] Initializing database
...

As it says, this attaches to the containers, and lets you see their output. This will be useful in case of errors.

As before, we can see the status as so:

$ docker-compose ps
Name               Command                  State              Ports       
---------------------------------------------------------------------------
mysql   /entrypoint.sh mysqld --ch ...   Up (healthy)   3306/tcp, 33060/tcp

We have a functioning database. Because the Compose file does not have a ports tag, the MySQL port is not exposed to the public. We already know this means exploring the database requires executing commands inside the container.

$ docker-compose exec mysql bash
bash-4.2#

While we could use the docker exec command, this also works.

bash-4.2# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
...
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| wpdb               |
+--------------------+
5 rows in set (0.02 sec)

mysql> ^DBye
bash-4.2# mysql -u wpuser -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
...

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| wpdb               |
+--------------------+
2 rows in set (0.00 sec)

mysql>

And as expected the root and wpuser accounts are set up, and have different rights.

Here's the MySQL data directory:

$ ls database/
#ib_16384_0.dblwr  binlog.index       ib_logfile0        mysql.sock         server-key.pem
#ib_16384_1.dblwr  ca-key.pem         ib_logfile1        mysql.sock.lock    sys
#innodb_temp       ca.pem             ibdata1            performance_schema undo_001
auto.cnf           client-cert.pem    ibtmp1             private_key.pem    undo_002
binlog.000001      client-key.pem     mysql              public_key.pem     wpdb
binlog.000002      ib_buffer_pool     mysql.ibd          server-cert.pem

This came about because of the volumes tag in the Compose file. This directory becomes /var/lib/mysql inside the container, and the MySQL instance created these files during its initialization.

To shut down the MySQL service:

$ docker-compose stop
Stopping mysql ... done

Notice that the docker-compose command does not take the file name on the command line. Instead, it looks at the docker-compose.yml file in the local directory. This means you can have one Compose project per directory, when using docker-compose. When using a Compose file on Swarm (docker stack) or AWS ECS or Azure ACI (docker compose), we can use any file name for the Compose file.

Summary

In this tutorial we've started learning about Docker Compose by deploying a MySQL instance. We've learned that there are parallels between the docker-compose and docker commands, and we've learned a little about the syntax of Compose files.

Links

About the Author(s)

David Herron : David Herron is a writer and software engineer focusing on the wise use of technology. He is especially interested in clean energy technologies like solar power, wind power, and electric cars. David worked for nearly 30 years in Silicon Valley on software ranging from electronic mail systems, to video streaming, to the Java programming language, and has published several books on Node.js programming and electric vehicles.