Over the weekend, I moved my blog from GitHub to GitLab. I now have a fully automated CI build on GitLab that grabs a NodeJS Docker image, downloads Hugo, UglifyCSS and HTMLMinifier via NPM, build and minify pages, and finally publishes to GitLab Pages on every Git merge to master.
I have been using Hugo Static Site Generator for over a year. Initially my blog was hosted on Google Firebase. Not long after, I moved to GitHub Pages and been using it for over a year. Be it Firebase or GitHub, they both worked very well for me.
If they have been working, why fix what is not broken?
TL;DR, to automate chores.
For full explanation, let me start by explaining my old chores of composing and publishing a new blog post:
public
folder. Then, minify CSS and HTML files. These commands were also in a batch file.public
folder to a local Git clone of my blog’s published content on GitHub. Lastly, commit and push to remote master branch.I spent the weekend improving the end-to-end process as much as I could.
Table below shows comparison among free tier options:
Google Cloud Repository | GitHub Pages | GitLab Pages | |
---|---|---|---|
Storage | 1GB | 1GB | 10GB |
Bandwidth per month | 10GB | 100GB | Unlimited |
Private repository | Yes | No | Yes |
Public repository | No | Yes | Yes |
Static site generator | No | Jekyll | Any* |
*Possible through the use of Docker images for CI builds. See example websites hosted by GitLab Pages.
I compared 3 different GitLab CI configurations. Configurations are in YAML format:
GitLab’s example uses a very small Docker image, Alpine Linux. The size is just 5MB.
The key lines are as follows:
image: alpine
variables:
HUGO_VERSION: '0.23'
HUGO_SHA: 'c9cf515067f396807161466c9968f10e61f762f49d766215db37b01402ca7ca7'
before_script:
- apk update && apk add openssl ca-certificates
- wget -O ${HUGO_VERSION}.tar.gz https://github.com/spf13/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz
- echo "${HUGO_SHA} ${HUGO_VERSION}.tar.gz" | sha256sum -c
- tar xf ${HUGO_VERSION}.tar.gz && mv hugo* /usr/bin/hugo
- hugo version
The variables need to be configured. They are HUGO_VERSION
which is the version to download and HUGO_SHA
which is a SHA256 hash of the Hugo release.
It will get you going if you need just the bare minimum.
Hugo’s example eliminates the need to maintain the YAML file by not needing to update version and SHA256 checksum of Hugo releases. Its Docker image already has Hugo in it.
image: publysher/hugo
pages:
script:
- hugo
artifacts:
paths:
- public
only:
- master
This approach is the most simplified option that I could find.
I setup my .gitlab-ci.yml
to use NodeJS Alpine Docker image.
image: node:6.11.2-alpine
before_script:
- apk update && apk add openssl ca-certificates
- npm install
- PATH=$(npm bin):$PATH
- hugo version
pages:
script:
- npm run build
artifacts:
paths:
- public
only:
- master
Undoubtedly more complicated than the other two options but there are reasons.
I leveraged on NodeJS NPM Package Manager to install Hugo binary as well as CSS and HTML minifiers. The version of Hugo on NPM is slightly outdated but this is fine by me. The minifiers are used to shrink files after Hugo has generated them.
From the YAML file above, the build command that GitLab CI will call is npm run build
which in turn runs the command found in my package.json
. Read next section.
Note: This Docker image does not contain Python. Python is required for Pygments to work. If you have not heard of Pygments, it is a server-side syntax highlighter. To reduce my blog’s dependencies, I chose to replace this with HighlightJS, a client-side alternative.
Below is a JSON file that NPM understands. It is placed at root of your Hugo source folder:
{
"name": "www.leowkahman.com",
"preferGlobal": true,
"version": "0.0.1",
"author": "Leow Kah Man",
"description": "Leow Kah Man - Tech Blog",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://gitlab.com/kmleow/kmleow.gitlab.io.git"
},
"engines": {
"node": ">=6.00"
},
"scripts": {
"start": "hugo server --theme=leow-kah-man --buildDrafts --disableLiveReload=true",
"build": "hugo && uglifycss ./public/css/custom.css --output ./public/css/custom.css && html-minifier --collapse-whitespace --keep-closing-slash --file-ext html --remove-comments --minify-css true --minify-js true --input-dir public --output-dir public"
},
"dependencies": {
"html-minifier": "^3.5.3",
"hugo-bin": "^0.12.0",
"uglifycss": "0.0.27"
}
}
npm start
so that Hugo can generate files as soon as it gets saved.npm run build
to generate files using Hugo and then minify them using UglifyCSS and HTMLMinifier. This command should not need to be executed by yourself unless you are testing the build process.All that is left is pushing these along with Hugo source files into master branch on GitLab. The CI is triggered automatically upon performing Git push to GitLab. If the build fails, you will receive an email notification almost immediately. Otherwise, the website should be published in about a minute.
You can find additional information, i.e. SSL/TLS setup on custom domains under GitLab Pages Getting Started guide.