Make your CI pipeline fast and awesome with Gitlab Container Registry

Maciej Głowacki
Daftcode Blog
Published in
7 min readApr 4, 2018

--

Illustration by Magdalena Tomczyk

YYou 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:

This will run tests and check your code with rubocop. It is a good start

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.

Remember to add them to your project’s readme, because it’s been scientifically confirmed that the more badges you add the better your project becomes. True story

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:

You can of course ignore those comments as long as there is no AI sentient enough to stop you from doing this

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:

Clicking on this orange exclamation mark will tell you that it’s the check_gemfile job that didn’t pass

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:

If you don’t see it, you may need to update GitLab or enable Container Registry in configuration

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:

This solution is a bit hacky (things like overwriting BUNDLE_GEMFILE environment variable), but it works well enough for running a basic test suite. Setting LANG is optional, but the default ASCII setting will cause you some problems sooner or later — better change it now.

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

The dog symbolizes “it” and wrapping symbolizes wrapping — obviously

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 👏👏👏

You can also follow us on Facebook, Twitter and LinkedIn.

--

--

Freelance Ruby Magician. I do full-stack, photography, learn Chinese and stuff.