This is a guide for the integration into GitLab CI of the code for the Dockerizing a Flask-MySQL app with docker-compose article.
GitLab, just like GitHub, is a web-based versioning control system powered by Git. In addition, GitLab aims to support the whole development lifecycle. One of its features is Continuous Integration (CI).
The CI feature triggers a pipeline for building and testing upon every code submission. This ensures automatically that the latest code works and allows that multiple contributors can continuously integrate (merge) their code into the main repository with confidence.
Nevertheless, every CI system requires some extra effort from the developers: They have to provide instructions on how the code builds and how the application is tested.
The rest of this guide describes the .gitlab-ci.yml file that contains the commands for the Docker Compose, Flask, and MySQL combination, based on the aforementioned article and a Stack Overflow related answer.
Every time a commit is submitted, one or more Docker containers are spawned in GitLab CI. These Docker containers serve as an isolated volatile environment to check the whole codebase, with the latest changes included of course.
GitLab CI is enabled if a .gitlab-ci.yml
file exists in the root directory of
a GitLab project. All the code snippets that follow are taken from this
project's .gitlab-ci.yml. This file starts with the
declaration of a Docker image. In our case, we will use the official
docker/compose
image. Besides, we need to execute several docker-compose
commands in it.
image:
name: docker/compose:1.24.1
We stick to a specific docker/compose
image version, e.g. 1.24.1
. This is
done because, as we can see in the related
versions page, no default
latest
tag exists.
entrypoint: ["sh", "-c"]
The above statement is needed in order to execute shell commands. If we omit it,
only plain docker-compose
commands can be executed.
We are going to execute docker
commands inside a docker/compose
container.
This isn't as straightforward as it seems, because docker
is designed to run
directly in the host machine and not inside a Docker container. This limitation
can be eliminated using the Docker in Docker dind
service.
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
The above two variables are set according to GitLab instructions, to facilitate the communication between the Docker container and the host machine.
In GitLab CI, a job is created by simply writing its name followed by a colon.
Below, we define the Build and Test
job.
Build and Test:
before_script:
- apk add --update curl
- docker version
- docker-compose version
The commands of a job are divided into three sections: before_script
,
script
, and after_script
. In the first section above, we install curl
, and
we print the versions of the Docker commands.
script:
- docker-compose build
- docker-compose up --detach
The main commands are under the script
section. The containers are being run
in detached mode. This allows the execution of more commands after
docker-compose up
. With the following commands we ensure that the application
and database are up and running.
- until [ $(docker-compose logs app | grep -c "Running on ") -eq 1 ];
do
sleep 2;
done
- until [ $(docker-compose logs db | grep -c "ready for connections") -eq 2 ];
do
sleep 2;
done
docker-compose logs
command gives the output of the corresponding service. The
database should output twice the ready for connections
message: one at the
beginning and another after the database is fed with the database schema.
Last but not least, after all services are initialized, we should check that
everything works as expected. We store the expected output into the expected
file. The - >
symbols below are used to overcome the need of escaping a bunch
of quotes that follow.
- >
echo -n '{"favorite_colors": [{"Lancelot": "blue"}, {"Galahad":
"yellow"}]}' > expected
Then, we get the real server response via curl
. Instead of hitting the
localhost
domain, we use the docker
keyword, due to the Docker in Docker
limitations.
- curl -f http://docker:5000 > output
Finally, we compare the expected to the real output. If the comparison or any of
the commands fail, the success
file will never be created.
- diff expected output
- touch success
If the success
file doesn't exist, all the logs are printed for debugging
purposes.
after_script:
- if [ ! -e success ]; then
docker-compose logs;
fi
See the output of the CI job here.