Michael Blum

Developer from Chicago

Building a Blog Part 3 - Continuous Integration with Gitlab CI

Why CI a simple blog?

Just like we write automated unit tests for our applications, anything we’re pushing to the Web should be stable and we’re confident we didn’t break our site with an errant //JS or <HTML> tag. To do this, we’ll attach an integration service listening to our commits to the master branch.

Choosing a Platform

Since I’m running my blog outside of Github Pages, I can’t depend on integration servies such as Travis CI and Github’s ecosystem of integrations. Have no fear, there are many Continuous Integration solutions out there for a variety of purposes. Here are a few:

  1. Jenkins (used to be Hudson)


While I’m familiar with Jenkins, I know from experience it requires quite a bit of RAM as well as needing a JVM. Its a great choice for running Java builds with Gradle.

  1. Travis CI (integrates with Github)


If we were hosting this blog on Github Pages, Travis would be a no-briner.

  1. Gitlab CI


Since we spent a whole blog post deploying and securing a Gitlab instance, it makes sense to keep going in this vein.

Installing Gitlab CI

As the instructions may change, here is the Gitlab Runner repo:

Gitlab CI Runner

This will setup the hooks and listeners for our commits to start a build.

I elected to install Docker on the Gitlab server to isolate CI builds from affecting the server.

curl -sSL https://get.docker.com/ | sh

this gives us more flexibility when creating our .gitlab-ci.yml configurations for our projects.

When the installer asks for the registration details, you’ll find that here in your Gitlab install: Project > Settings > Runners.


Runners break down a build into three main phases:

  • Build
  • Test
  • Deploy

Using our knowledge of Docker we can build isolated and reproducable builds. Here’s the one I’ve created for this blog:


image: ruby:2.3

  - build
  - test
  - deploy

  - apt-get update >/dev/null
  - apt-get install -y locales >/dev/null
  - echo "en_US UTF-8" > /etc/locale.gen
  - locale-gen en_US.UTF-8
  - export LANG=en_US.UTF-8
  - export LANGUAGE=en_US:en
  - export LC_ALL=en_US.UTF-8
  - bundle install --jobs $(nproc) --path=/cache/bundler
  stage: build
    - scripts/cibuild
    - master

The encoding changes you see are a solution to Invalid byte sequence in US-ASCII - an issue I only saw in my Ubuntu environments, not locally on OS X.


#!/usr/bin/env bash

set -e # halt script on error

bundle exec jekyll build
bundle exec htmlproofer _site

based on Jekyll’s recommendation: Jekyll CI

Validation Gotchas

  1. <img> tags need an alt=

  2. Markdown anchor links are all lowercase with no spaces, periods, or question marks.

a header like # .gitlab-ci.yml resolves to #gitlab-ciyml and ## Installing Gitlab CI resolves to #installing-gitlab-ci

Luckily, the htmlproofer gem catches these sorts of breaks as well as many others.

Commit > Push > Build

Once we’re ready to commit we merge our feature branched article into trunk and run git push gitlab master we see the build kicking off:

build pending

build running

build failed


It appears our linter caught a broken link in the site. Lets fix that.

build passed

build passed logs


Invalid byte sequence in US-ASCII

By default, the ruby:2.x Docker image uses US-ASCII instead of UTF-8, which will cause validation errors when linting the generated site for errors.