One of the reasons I moved to GitLab is that it provides free private repos as well as free CI. It’s CI is based around Docker images which makes it dead easy to build any sort of project from GitLab.

I thought I’d share with you here the simple configuration I use to build things (such as this blog) and put it on to my web-hosting.

To tell GitLab you want to use their CI, you create a file .gitlab-ci.yml in the root of your repo. If this file starts with image: then GitLab knows it’s a Docker build and will use the appropriate runner. However, this is a general file and you can use any the various runners for doing your builds (see GitLab’s documentation but Docker is as general as it gets.

You must specify a Docker image from The Docker Hub although you can also use a local GitLab repo with your own images if you want. As the example I’m showing here is for this Jekyll-based blog (which is Ruby-based), we use a Ruby image:

image: ruby:2.3

The next section contains some script-wide variables that can be used for keeping your scripts tidy. So, I ensure that Jekyll is going to be in production mode:

variables:
  JEKYLL_ENV: production

Next comes some bigger sections. The build section tells GitLab how to build your code after it has checked out your repo into the container. The deploy section tells GitLab how to deploy your code after it has checked it out into a container. Each of these sections runs as a separate docker process in GitLab and they do not share the same container.

So, the first section is this:

1
2
3
4
5
6
7
8
9
10
11
build pages:
  stage: build
  before_script:
    - bundle install
  script:
    - bundle exec jekyll build
  artifacts:
    paths:
      - _site
  only:
    - master

We give the build a name: pages and it’s the build stage. In the before_script section (line 3), each line gives a shell command to run before we do the build. Here’s we’re ensuring we have all our gems. Each line in the script section (line 5) performs the build.

One of the most important parts here is the artifacts section (line 7) which allows us to pass the result of the build on to another stage of the pipeline. If we don’t do this, when the Docker container shuts down we lose everything. Here we tell it to remember the contents of _site, which is where Jekyll outputs the fixed HTML to.

So that’s the build done. Now we need to deploy that to my hosting. If you’re hosting on GitLab or GitHub, moving this output to public and committing the result is enough.

However, I need to SSH to my own hosting provider to put my blog up there. Doing that requires us to set some SSH keys in the server and GitLab to ensure that the container can talk to my hosting.

To do this, generate a key-pair on your server (on the hosting box) with:

ssh-keygen -t rsa -C "your.email@example.com" -b 4096

Grab the contents of the ~/.ssh/id_rsa file and in GitLab’s settings for your project, under Settings > Pipelines add a new variable called SSH_PRIVATE_KEY and paste the contents of that file as the value. The environment can stay *. This lets GitLab log into your host without requiring a password; but only if the server is trusted.

To allow the server to be trusted, you also need to generate a value for SSH_SERVER_HOSTKEYS and add that as a variable. You can do that with:

ssh-keyscan <YOUR-SERVER>

Paste the result of that command as the value of the new variable.

Alone, these two variables do nothing, but in our deploy script we update the container with these keys so that it has access to the host. These keys are stored securely in GitLab, behind your username and password, and the Docker container is only running for the duration of the build.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
deploy to site:
  stage: deploy
  variables:
    GIT_STRATEGY: none
  before_script:
    # Install ssh-agent if not already installed, it is required by Docker.
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'

    # Run ssh-agent
    - eval $(ssh-agent -s)

    # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
    - ssh-add <(echo "$SSH_PRIVATE_KEY")

    # In order to properly check the server's host key, assuming you created the
    # SSH_SERVER_HOSTKEYS variable previously, uncomment the following two lines
    # instead.
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo "$SSH_SERVER_HOSTKEYS" > ~/.ssh/known_hosts'
  script:
    - scp -r _site/* <username>@blog.dupplaw.uk:<path in my hosting>
  dependencies:
    - build pages

There is more about the SSH process on GitLab’s documentation.

Once all the SSH keys are set up, a simple scp (line 21) is all that’s needed to get the files on to my hosting.

One interesting variable in the deploy script, is the GIT_STRATEGY variable defined on line 4. This tells GitLab not to bother checking out your repo into the container. The deployment is only using the result of the previous stage, so it doesn’t need the repo. This saves time during the build.

GitLab’s CI is incredible. It’s free and easy to use and supported by Digital Ocean, one of the big Docker hosting suppliers. I’m using it for lots of my personal projects now. How are you using it?