Make your CI pipeline fast and awesome with Gitlab Container Registry
You must have heard of Continuous Integration. The chances are, you’re already using it in your projects (or at least plan to do so). There are a lot of different tools that let you get the job done, but today we’ll focus on doing cool things with Rails project using GitLab’s built-in CI.
Getting started
Starting with GitLab CI is straightforward. When creating a new project we can choose a “Ruby on Rails” template which contains a very basic CI setup file.
If you don’t trust a machine to setup your project (or have one that’s already been setup) the good place to start is GitLab’s Getting started with GitLab CI/CD. It will tell you to create .gitlab-ci.yml
file in your repository and even give an example for Ruby on Rails. How cool is that?
Whichever way you chose, you’ll have to do some simple changes. Choose proper ruby version, select different testing framework or setup the database. In the end, you should have a .gitlab-ci.yml
that looks similar to this one:
After you push this file to the repository and wait for a while you will see that all tests have passed. Your commit will get a green checkmark to show this 💚
Adding awesomeness
Now that you’ve got the basic script running you may want to make it do all sort of cool things.
Code coverage
First, add code coverage support for tests. Adding simplecov to your application is easy, but how do you make GitLab use it? Open your repository in GitLab and go to Settings > CI/CD > General pipelines settings
. Look for Test coverage parsing
section and use the example they show you there. It’s that easy 👏
And if you scroll the settings page a bit further you’ll find badges for pipeline status and code coverage.
Apart from coverage badge, you can now see coverage in Merge Requests. This lets you find any missing tests at a glance.
Code quality
Another thing that you can improve is rubocop support. Right now it finds offenses in your code and causes the pipeline to fail. But to see what’s wrong you have to open the job details and read through the logs. You can definitely improve this experience somehow.
And that’s when pronto comest to help. This nifty gem lets you integrate all kinds of different tools with GitLab. It makes their output appear as comments in Merge Requests. The first step is to add pronto and some runners to your Gemfile
:
group :ci do
# Quick automated code review of your changes
gem 'pronto'
gem 'pronto-brakeman', require: false
gem 'pronto-flay', require: false
gem 'pronto-reek', require: false
gem 'pronto-rubocop', require: false
end
Now, instead of doing bundle exec rubocop
, you can execute bundle exec pronto run -f gitlab -c origin/master
. To make it work in CI, you have to give pronto an API key that will let it write comments. You can generate this key in Access Tokens
tab in profile settings (you may want to have a separate account dedicated to writing automated comments).
Now you can configure PRONTO_GITLAB_API_PRIVATE_TOKEN
and PRONTO_GITLAB_API_ENDPOINT
in Settings > CI/CD > Secret variables
.
You’ll also have to tell apt-get
command to install cmake
. Pronto needs it to build some required libraries.
From now on your code will get checked with every push, and pronto will report all offenses as comments. You’ll see them appear under the relevant line of code:
Updates
Another thing that is easy to track with GitLab CI are updates for your project dependencies. Regular updates are important for two reasons: security and future maintainability.
You can handle the first one with a gem called bundler-audit, It has a list of vulnerable gems, and uses it to check your Gemfile
. Edit ci
group in your Gemfile
and add bundler-audit there:
group :ci do
...
# Patch-level verification for Bundler
gem 'bundler-audit', require: false
end
To make it run in your CI pipeline add bundle audit check --update
the same way you’ve added pronto before.
Now, every time you push some changes it will check if any of your gems have known vulnerabilities. If it does, your pipeline will fail so you’ll know that there is an urgent update required.
But you don’t need to have a vulnerable gems to update dependencies, do you? 😅 Luckily, Bundler has a built-in tool that helps you check for gems updates. All you have to do is execute bundle outdated --strict
. The strict
flag tells Bundler not to report gems that you’ve version-locked in Gemfile
. Since most gems updates are minor fixes that can wait you don’t want your pipeline to fail on this command. To make it less annoying, you can allow a CI job to fail. Or in a language of GitLab config:
check_gemfile:
script:
- bundle outdated --strict
allow_failure: true
When it finds some new gems versions available, you’ll get a warning, which looks like this:
Making it faster 🚀
It all works, but as you will notice it’s not as fast as you might expect. Your basic pipeline will spend around 10 minutes setting up. The worker spends this time on installing dependencies and extracting cache. Can you skip those steps somehow?
Container Registry
As it turns out, GitLab has a cool feature called Container Registry. It lets you upload custom docker image and use it to execute CI jobs. Open your project and look for Registry
button which looks like this one:
You’ll find a short step-by-step instruction there. It explains how to configure Docker for use with Container Registry. To create Docker images you must create a Dockerfile
describing your setup. It should do the same thing that you’ve configured inside .gitlab-ci.yml
:
- choose Ruby version,
- install build dependencies,
- install gems.
If you use such image for CI execution, all gems will be already installed. It won’t have to spend time setting up dependencies and building libraries. Your basic Dockerfile
may look like this one:
First, it installs node
(required by Rails) and cmake
(required by pronto
). Then, it uses local Gemfile
to install all gems required for testing.
Now, you’re ready to build
and push
according to the guide from Container Registry. Choose some tag for your image, so you can have different ones in the future and know which one is which. Let’s name this one master
.
Configuration
The last step is changing .gitlab-ci.yml
to make it use your custom image. All you have to do is change image
value, and get rid of cache and most build related commands. You only need bundle install
, so it can install all new gems that might be missing your image. Final setup, with all awesomeness added, looks like this one:
It should be much faster now, because it doesn’t have to install most of the dependencies. They are already there. Your pipeline will finish in under a minute now. There are ways to optimize this (you can skip bundle install
in updates
job), but it’s already good enough.
And if you happen to change your Gemfile
you only have to update your Dockerfile
, and push a new image. You can even overwrite your master
image so you won’t have to change anything inside .gitlab-ci.yml
.
How cool is that? 💆
Wrapping it up
And that’s all you have to know to make your GitLab CI faster. If you followed the steps I’ve described, you should now have a setup that:
- measures your code quality,
- runs tests and checks code coverage,
- watches for outdated dependencies.
Apart from thath, thanks to Container Registry, you should have reduced pipelines runtime down to a minute.
It should be enough to get you started or help you make your existing pipelines faster. If you have some other tips for awesome things to add to CI go ahead and share it in the comments below 🙌
If you enjoyed this post, please hit the clap button below 👏👏👏