05 Jun 2018

Github has worked great for me over the past many years, and I've been a paying customer for about 8 years. That's not likely to change but I took the recent acquisition by Microsoft as a prompt to explore other options as I saw many comments praising gitlab, and specifically their continuous integration options.

I found that really interesting as I had found the plugin restrictions in Github very limiting - I want to be able to run preprocesses on my files so I don't have to type or copy and paste so much.

This post explores how I migrated one of my sites there.

Outline of the process

  1. Import old project (PRs, issues etc)
  2. Create a .gitlab-ci.yml file (build pipeline) and build
  3. Check that page is live under gitlab.io domain
  4. Add a custom domain and SSL support using Let's Encrypt (certbot)
  5. ...

Optionally: run your own runner to do the build (future post).

Import old project

Option 1: Import using Gitlab's wizard. Very easy but you give access to all projects you have access to. Some client companies may not be fine with it. At the very least remember to disable access on Github settings when you're done with the import.

Option 2: If you just need the git history.

  • create a new empty repo in gitlab,
  • add that repo as a new remote on your current github repo local copy,
  • push to the remote,
  • remove your local repo copy, clone the gitlab one and you should have everything there.

Create a pipeline job definition

We will be creating a file called .gitlab-ci.yml on the root folder.

Very brief introduction to gitlab's pipelines

  • They are based on a YAML file describing the steps and scripts to run
  • They run inside 'runners' that create containers to run the pipeline jobs
  • There are shared runners owned by Gitlab but you can also have local runners
  • Jobs can generate artifacts that pass from one step to the other or are end results

Brief introduction to Gitlab's Pages

  • If there is a job called pages that generates an artifact that contains a folder called public, everything under that folder will be served as static contents
  • By default, this will be served under https://GITLABUSERNAME.gitlab.io/PROJECTNAME

Example file for a jekyll project using ruby 2.4.4

image: ruby:2.4.4

  JEKYLL_ENV: production

  - bundle install

  stage: deploy
  - bundle exec jekyll build -d public
    - public
  - master

and here's the Gemfile I was used for jekyll already:

source "https://rubygems.org"
ruby "2.4.4"

gem 'jekyll','3.6.2'
gem 'jekyll-paginate'

You should never lose the ability to build locally, so keep the pipeline script ultra simple.

It's important to match the ruby versions between Gemfile and pipeline, otherwise the runner would be launched with a different version.

Check your page under gitlab.io domain

Just by pushing the CI file it will create the pipeline and it will build every time there is a push to master.

After a first build passes you will be able to access the site. If you don't, check where you went wrong as all the rest is meaningless until you get to this stage.

Now you can rest, keep playing with your site and check that everything looks ok. Depending on how you implemented it, it may not be able to find CSS and images, but don't panic - just use view source to check the URLs will be ok when it's in its own domain (or fix your jekyll config so that it is)

Add a custom domain and SSL support

A nice github feature is that you, simply by checking a box, get a valid SSL certificate (they do internally all the let's encrypt domain validation). On Gitlab pages this is a manual process. You'll find lots of guides for this, but here's what I did (word of advice: the best approach is probably to do first adding a domain without SSL and then adding SSL support, but I did all at the same time)

Install certbot:

apt install certbot

You can use yum, brew or whatever your poison is.

Decide of a home to store your certificates. I decided this certificate is pretty much irrelevant so put it inside the container itself:

export CERTDIR=~/onlydognews.com/certs

Generate a certificate request and send it to letsencrypt:

certbot certonly -d onlydognews.com --work-dir ${CERTDIR}/work/ --logs-dir ${CERTDIR}/logs/ --config-dir ${CERTDIR}/config/ --manual


Obtaining a new certificate
Performing the following challenges:
http-01 challenge for onlydognews.com
Are you OK with your IP being logged?
(Y)es/(N)o: y

Create a file containing just this data:


And make it available on your web server at this URL:


Press Enter to Continue

This turned out to be a bit tricky since my domain was still pointing to the old Github project. So I cloned that repo and added a commit to publish that file, in order to verify my domain.

I added this to _config.yml, the Jekyll configuration file:

include: [.well-known]

I created a new file under the path instructed by cerbot and pushed both changes upstream, which made the file available online. Then continued the certbot process.

Waiting for verification...
Cleaning up challenges
Non-standard path(s), might not work with crontab installed by your operating system package manager

 - Congratulations! Your certificate and chain have been saved at:
   Your key file has been saved at:
   Your cert will expire on 2018-09-02. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"

There's an important bit there, we will have to remember to renew the certificate in three months. Certbot can automatically update some servers but not Gitlab as far as I'm aware.

The result of all this setup process is

'Live' folder

  • DOMAIN/privkey.pem: the private key for your certificate
  • DOMAIN/fullchain.pem: the certificate file used in most server software.
  • DOMAIN/chain.pem: we don't need it
  • DOMAIN/cert.pem: we don't need it

'Renewal' folder:

  • DOMAIN.conf: File that certbot will use for renewal

'Archive' folder: History of all the certificates generated. This will contain just the one just created.

'Renewal-hooks' folder: Empty folders for deploy, pre and post hooks.

'Keys' folder: Certificate private keys used. I have a few because I tried this process a few times.

'CSR' folder: Certificate requests used. Again, one for every try.

'Accounts' folder: Output of the registration process

  • meta.json: creation date, host name that issued the request
  • private_key.json: private key used during registration
  • regr.json: data used during registration, contact details, terms and conditions

Installation into Gitlab

Go to your project/pages/domains/new

Check the enforce SSL option and add your domain name

In Certificate(PEM) add the fullchain.pem text contents

In Key(PEM) add the privkey.pem text contents

It will show:

This domain is not verified. You will need to verify ownership before access is enabled.

To do so, from your DNS provider create a new DNS record:

  • Type: TXT
  • Name: _gitlab-pages-verification-code
  • Value: gitlab-pages-verification-code=XXXXXXXXXXXXXXXXXXXXXXXXXXXX

Important: the gitlab page displays the full Name (including the domain, as in _gitlab-pages-verification-code.YOURDOMAIN.com), but on the DNS settings you just add the host part

Now we can finally move our domain to the new one. In the settings page Gitlab will show a CNAME entry that you should use. This is fine if your site is on a subdomain (for example www), but if it's the root (as in gatillos.com), you can't create a CNAME of it. This is a DNS restriction that some people are not aware of: the root domain is called APEX domain and is used by a number of things. If you create a CNAME of it you can lose control of MX and similar registers.

To find out the IP address you need, simply lookup the domain showed on the settings page:

dig +short YOURCNAME.gitlab.io

Which means we need to add an A record on your DNS, something like:

a   @   1 hour   

Final result:

(Follow me on Twitter, on Facebook or add this RSS feed) (Sígueme en Twitter, on Facebook o añade mi feed RSS)