In this first exercise we will use a provided docker-compose
to start and stop a collection of containers (services). In this case it is a python container that is exposed in an nginx server and talks to a mongo database. It is important to realise how you can compose complex applications using individual simple components, containers, which fulfill a single function.
- Go to the folder
exercise-04
move into the foldernginx-flask-mongo
- To start the application all we need to do is
docker compose up
The application runs in the foreground, so we see the logs of all the different containers.
- In a different terminal, check that three services are up and running:
docker ps
- Ensure you are in the same folder where the
docker-compose
file is, and run
docker compose ps
Notice how this only lists the containers defined in that docker-compose.
-
Access the URL http://localhost:80 and check that it works (you should see Hello from the MongoDB client!).
-
Go back to the original terminal and press
Ctrl+C
to stop. -
Usually it is preferable to start in detached mode:
docker compose up -d
-
You can still see the logs of the containers:
docker compose logs web
You can also keep the logs open to follow future changes:
docker compose logs -f web
-
Many of the commands we explored in the earlier module also work here. For example you can do
docker compose exec mongo mongosh --eval "db.users.insertOne({name: 'jonas'})"
-
To remove everything use the
docker compose down
command.
The goal of this exercise is to define a docker-compose.yml
that would allow us to run a copy of wordpress using mysql database to persist the data.
-
In a new folder, let's start by creating a
docker-compose.yml
file which defines a single service for wordpress. You can use the official imagewordpress:5.7.1-apache
. By default this image runs wordpress in port 80; expose it to a local port in your machine so you can browse to it with your own browser. -
You can verify the syntax of your docker-compose by running
docker compose config
. If all is well, it should output a copy of your docker-compose. -
You can then use
docker compose up -d
to start up the service. You should be able to open wordpress in your browser.
- Next add another service named
db
to run MySQL - again, use the official imagemysql:8.0.19
. You don't need to expose any ports in this case. - Execute again
docker compose up -d
to start up this new service. Notice that as you have not modified the wordpress service, it has not been recreated. - Check the state of MySQL by executing
docker compose ps
. You will see that thedb
service has exited. In order to find out why, executedocker compose logs db
. You should see that MySQL tried to start up but it requires some environment variables defining some initial configuration like the Admin password. Add the following variable to the docker compose:
- MYSQL_ROOT_PASSWORD=someDifficultPassword
- Try to bring the service back up again.
- Inspect the logs and verify that mysql is running successfully
- Lets define a user and an initial database for wordpress. You do this by creating extra environment variables in the
db
service:
- MYSQL_DATABASE=wordpress_db
- MYSQL_USER=wordpress_user
- MYSQL_PASSWORD=wordpress_password
- You need to configure wordpress to leverage the
db
service to persist information. This is done by defining the following variables which tell wordpress how to connect to the database:
-
WORDPRESS_DB_HOST=db
-
WORDPRESS_DB_USER=wordpress_user
-
WORDPRESS_DB_PASSWORD=wordpress_password
-
WORDPRESS_DB_NAME=wordpress_db
-
WORDPRESS_TABLE_PREFIX=wp_
Notice how we use the name of the database service as defined in the docker-compose file to indicate the host name of the database to the wordpress service. Services in docker compose can reach each other using the service name as hostname for the destination, this is called DNS service discovery, it will only work for services using the same network, if no network is defined, docker compose will create a default one and connect all services to it.
- Finally, you should add
depends_on
as an extra key to thewordpress
service to express the dependency, sincewordpress
now requires thedb
service to function correctly. - Invoke
docker compose up -d
again. - Execute
docker compose logs -f db
to check the database is starting correctly with the new configuration. This process can take a few seconds. - Verify that the site is still displaying correctly in your browser.
If you get the error Error establishing a database connection
it can be due two reasons:
- The database is still starting, check for the message
ready for connections
in the logs, if the database is ready and the problem persists go to the next point. - Try running
docker compose down
and bring it up again. This happens because you tried to set up a database connection from the web interface in the previous step and the docker container for wordpress created a config file with wrong credentials. When you brought up the database, the wordpress container was not recreated because its definition did not change, but we need it to start from a clean state, that's why we want to delete it and start a new one.
If you run docker compose down
and up
again, the data would be lost. In order to keep it, you would need to define a volume to persist the data.
-
Declare a new volume at the end of the
docker-compose.yml
services: ... volumes: db_data:
-
Use the declared volume in the service where you need it
services: db: ... volumes: - db_data:/var/lib/mysql
-
Restart the application with
docker compose up -d
-
This volume will not be removed when doing
docker compose down
hence your application data is persisted. If you wish to delete it you can rundocker compose down -v
.
- Check the existing networks:
docker network ls
- Notice that although you didn't create them manually, several networks are shown: they are automatically created for each docker-compose by default, so that their services can communicate.
- Check the existing volumes:
docker volume ls
It is good practice to add healthcheck to your container - this will allow Docker to react and restart a container when it is still running but it is not functioning correctly (for example stuck in a infinite loop). In general the healthcheck is a command that will run inside the container; its exit code inidicates whether the conatiner is healthy or not.
For MySQL, we can use the msyqladmin ping
command to verify if it can accept connections.
Add the following inisde the db
service:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping --user $$MYSQL_USER --password=$$MYSQL_PASSWORD | grep --silent alive"]
interval: 3s
timeout: 1s
retries: 3
Notice how we need to pass the user and password. The double dollar
$$
is required to escape it inside the docker comopse file. Also thegrep
command is required becausemysqladmin
returns exit code 0 when supplying a bad password, which could lead to false positives. If you need to debug health checks you can inspect their output using the commanddocker inspect --format "{{json .State.Health }}" <container name> | jq
Now you can change the depends_on
to wait for the database service to be healthy, not just ready, before starting the Wordpress container.
- The goal of this exercise is to convert the
docker run
for nginx that we used in the exercise-03 to a simple docker-compose.
We usually use docker-compose.yml for defining applications with 2 or more services (not just one like this example). This exercise with a single service docker-compose.yml is intended for pedagogical goals. However, even for a single service, the clearer syntax of the docker-compose.yml can be beneficial. Another advantage is that the file could be committed to source control.
- Please, transform the next docker run into a docker-compose.yml and verify that it works as expected:
$ cd nginx $ docker run --rm -p 8888:8080 -e NGINX_PORT=8080 -v ${PWD}/index.html:/usr/share/nginx/html/index.html -v ${PWD}/conf:/etc/nginx/templates nginx
As seen above, docker compose allows to start multiple containers in a very easy fashion. However the same functionality could be achieved using independent Docker commands. As an example, you could run Wordpress and MySQL doing the following:
- Create a volume (it will be used by the database for persisting the data)
docker volume create db_data
- Create a network (it will be use by both the database and Wordpress in order to see each other)
docker network create wp-app-network
- Run a MySQL database with the required configuration:
docker run -d -p 3306:3306 -p 33060:33060 --network wp-app-network --network-alias db -e MYSQL_HOST=mysql -e MYSQL_ROOT_PASSWORD=somewordpress -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=wordpress -v db_data:/var/lib/mysql mysql:8.0.19
- Run a Wordpress server using the previous database
docker run -d -p 80:80 --network wp-app-network -e NGINX_PORT=8080 -e WORDPRESS_DB_HOST=db -e WORDPRESS_DB_USER=wordpress -e WORDPRESS_DB_PASSWORD=wordpress wordpress:5.7.1-apache
- Access http://localhost:80 and check that it works.
- As you have just experienced, using Docker Compose is much easier and provides a much better experience.