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
- https://docs.docker.com/compose/compose-file/compose-file-v3/
- https://docs.docker.com/compose/reference/overview/
- https://compose-spec.io/