Continuous Integration with GITLAB CI and Docker
Continuous Integration simply put is a software development practice where a group of developers working on smaller modules of a larger application integrate, build and test their changes as early and as often as possible. Without the right software tooling, this could be a very tedious and time-consuming task eating away a sizeable chunk of developers productive time. In this article we are going to learn how continuous integration can be achieved using GITLAB CI and Docker
Why GITLAB CI?
This question makes perfect sense in today’s world because there are so many options to choose from. We will set aside a comprehensive comparison of different CI tools for a different article, in this article, we will focus on GITLAB CI.
Here are a few things we will learn from this article:
- Why GITLAB CI?
- Creating a simple hello-world app
- Setting up a GITLAB CI Project
- Testing the App
Let’s start by answering the purpose for this article, one of the best if not the best features of GITLAB CI is that its integrated with GITLAB version control system. Not having to switch between different tools for SCM and CI/CD and not having to deal with the integration between them is a great benefit.
Here are few other advantages of GITLAB CI:
- YAML based build configurations: Not having to write the build instructions in any scripting language makes it easy to adopt and involves a smaller learning curve. YAML based files are easier to read and understand.
- Pipelines: Building pipelines consisting of multiples stages is easy. GITLAB CI visually separates, tracks and logs individual stages.
- Scalable builds: The CI builds can be distributed across multiple machines and it can be scaled on a need basis
Some other honorable mentions are Support for docker, Multi-platform, Multi-Language etc.
Let’s use a simple example to learn some GITLAB CI goodness. Lets get our hands dirty, shall we?
We will use the most basic
flask hello-world app to demonstrate this, We recommend you to follow along to get a better understanding.
The pre-requisites are python, pip. Here is a good installation example. Now that our workspace is set up. Let’s go ahead and create a folder called
hello-world which will be our project name. To create a flask project we would require the “
flask” python module, it can be installed by running the following command,
pip install flask.
Here is what our simple
helloworld.py script looks like,
from flask import Flask
app = Flask(__name__)
return "Hello World!"
if __name__ == "__main__":
Let’s go ahead and execute our program:
Now, our app is up and listening on port 5000, let’s go ahead and fire up a browser and visit
There it is! Our very first python web-app!
Let’s “Dockerize” our app therefore making our application deployable anywhere. It will also help us realize some cool features of GITLAB CI. Here is what our
Dockerfile look like:
COPY . /app
Let’s look at some of the highlights of the above
Line #1: We start with the python base image
Line #4: This is where we install the dependencies. Now that we already know that we need the
Flask python module for our application to work, we need to place all the python dependencies under the file named
requirements.txt. pip, which is our python package manager will parse through the file line-by-line and install the modules listed in it.
Since we have only one dependency, our
requirement.txt only has one entry as shown below:
Line #6: We fire-up our app right in the docker start-up thus, the
docker run command starts-up our application!
Let’s start by setting up an account on https://gitlab.com and create a “Blank Project” with any name you like, make it public and initialize with a README.
Once the project is created, we will be taken to the Project Landing Page as shown in the following image:
We will be focusing more on the GITLAB features labeled as,
- The GITLAB CI/CD and
- GITLAB Container Registry
In order to get started with the gitlab CI/CD capabilities, we need to start by writing our
.gitlab-ci.yml file which is used to manage our project.
This file must be placed at the root of our project and will contain the instructions to build our project. Let’s look at our
CONTAINER_IMAGE: registry.gitlab.com/codebabel/$CI_PROJECT_NAME #Workaround https://gitlab.com/gitlab-org/gitlab-ce/issues/23339
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
- build and stage
stage: build and stage
- docker build -t $CONTAINER_IMAGE:$CI_COMMIT_SHA .
- docker push $CONTAINER_IMAGE:$CI_COMMIT_SHA
Let’s break down our script,
Line #1: We start with the
image keyword, which defines which docker image must be used as the executor to build our application. We have chosen to the basic docker image with git installed on it.
Line #3: Here, we define a
service, which is simply another docker container that runs during our job and links to our Docker image that the
image keyword defines.
We use the
docker:dind image as its recommended for building docker images using gitlab ci. Refer this article for other ways to achieve this.
Line #6: Here, we have defined the properties that will be referred during build time. The Property,
CONTAINER_IMAGE is the tag that we will be using for our hello-world application
Here, we are also using some inbuilt environment variables that GITLAB CI offers,
$CI_PROJECT_NAME : The GITLAB project name, in our example this is hello-world
$CI_BUILD_TOKEN : This is the API token that will authenticate us to push images to the GITLAB container registry
Here is a full list of all the GITLAB CI variables.
Line #11: The keyword
before_script defines the set of commands that will be executed before any job is executed. We will log in to the gitlab registry using the user
gitlab-ci-token and use the inbuilt
$CI_BUILD_TOKEN property to authenticate
Line #14: These are the different stages of our build job. Stages can be divided based on different tasks performed or phases of our build process and are declared globally using the
stage keyword. Eg: Compile, Test and Deploy could be “stages” in a simple project build.
In our example, we will be using only one stage,
build and stage
Line #16: The
job 1, keyword defines the script that needs to be executed and is bound to the
build and stage, stage declared earlier.
Our job executes 2 simple scripts, docker build and docker push, we version our images based on the GIT Commit SHA which is unique for each GIT commit.
Great! Now we are good to commit our
.gitlab-ci.yml file into our project repository. GITLAB CI automatically builds the projects once a commit is made.
Let’s introduce a ripple by making a dummy commit 🙂
There it is! as easy as that to setup a simple project
All the GITLAB CI pipelines can be accessed from the CI/CD link from the side panel which is shown in the following image:
Lets examine our build logs now:
Lets look at the GITLAB container registry:
The container registry shows the list of all the pushed images!
Now, Lets pull this docker image and see if its working as expected.
docker run -p 5000:5000 registry.gitlab.com/codebabel/hello-world:0e59df808367a684756c41e12aa3196368b0ad69
Lets check the app by accessing
There it is! Our docker app in its full glory.
We have reached to the concluding part of our article, we learnt that GITLAB CI is a really a winner in terms of providing integrated CI/CD capabilities. We spent almost no time in integrating our SCM tool to our CI/CD tool, setting up the CI/CD pipeline is seamless.
.gitlab-ci.yml is written in YAML syntax, there is no need to learn any complex scripting language.
This is the very first article on our blog! Thanks for patiently reading through it. The full project can be found under CodeBabel/hello-world feel free to fork it and try the project setup yourself!
Please Subscribe to our blog and share your feedback below!