Building a static website with Hugo on GitLab Pages

I have been meaning to create a blog for some time now. A few weeks back, I had helped a friend set up her professional website on Squarespace. The end product is super slick and was easy to configure. The benefit, of course, with going this route would be that everything is handled for you, from creating the contents of the website to setting up the hosting.

Unfortunately, I tend towards masochism when it comes to computers. This blog post describes the thinking that went into me building my new blog and highlights some of the more tricky steps involved in proceeding as I did.

Simply put, I wanted to create a static website “from scratch.” One big benefit of a static website over a dynamic one (like WordPress) in my rationalization of the pain I am willing to endure is that I get to avoid the unnecessary costs and headaches from running a web server. Since webpages do not need to be regenerated each time a webpage is requested, static websites strike me as the simpler of two options, but simple living is rarely easy living.

But who really wants to create webpages “from scratch”? I am not that masochistic. I have in the past tried one the most popular static website generator, Jekyll, which takes a folder of Markdown files and simple configuration to create a website root directory. Jekyll still seems like a perfectly fine option. However, after much consternation, I decided to give Hugo a shot because of its growing community of users and my general interest in the Go programming language it is written in.

The next decision was where to host the static website. In the past I have used Amazon S3 to host static websites. But I would rather avoid even the few dollars that it ends up costing a month. For this website, I tried to use GitHub Pages which is free but is intended for Jekyll users primarily. I could not figure out why it kept on complaining about Jekyll errors when my site was clearly not intended for Jekyll. Frustrated, I decided to give GitLab Pages a chance since they provide clear instructions for getting a basic Hugo website site deployed.

Getting started with Hugo was straightforward since they provide excellent documentation. Rather than use the outdated Hugo package for my Ubuntu distribution, I installed Hugo from source. For that, I first installed golang for Ubuntu, set up my environment variables, and then installed hugo:

sudo add-apt-repository ppa:ubuntu-lxc/lxd-stable
sudo apt-get update
sudo apt-get install golang
export GOPATH=$HOME/go
echo "\$PATH=\$PATH:$GOPATH/bin" >> ~/.zshrc
source ~/.zshrc
go get -v github.com/spf13/hugo

Ultimately, the most time consuming steps involved configuring GitLab to automatically deploy the website the way I wanted. Because I am using a Git submodule theme hosted on a non-GitLab server, the first problem was that I needed to define extra variables in the .gitlab-ci.yml to tell GitLab to recursively clone the project repository and to not verify the SSL certificate from any submodule’s Git web server:

image: publysher/hugo

variables:
  GIT_SSL_NO_VERIFY: "1"
  GIT_SUBMODULE_STRATEGY: recursive

pages:
  script:
  - hugo
  artifacts:
    paths:
    - public
  only:
  - master

Once my changes were pushed to the GitLab repository, GitLab triggered its continuous integration system to build and deploy the website using this configuration. I was able to monitor the whole process in the Pipeline view, which is a wonderful feature any user gets for free on GitLab. Once the site was deloyed and working at the provided GitLab Pages domain for the project, majorgreys.gitlab.io/tahirbutt.com, I configured my domain name provider to point to the GitLab DNS servers and setup a new custom domain with GitLab.

Voilà, http://tahirbutt.com/ was live!

But I was not done. I realized that I would have to do some extra legwork to get the new domain to support the secure HTTPS protocol. But why use a secure protocol for a personal website? My main reason was that it ensures that visitors to my website can trust that they are seeing what is actually the contents of my website rather than someone impersonating me.

However, I have been for many years living in blissful ignorance of how SSL certificates are acquired. Most of the time, personal websites come prepackaged with a new certificate for the user. For a building a website from scratch, I had go through a manual process which was not as easy as I would have liked.

Fortunately, after a few missteps, I found that GitLab provides a tutorial on securing your pages domain with TLS/SSL certificate using the free service provided by Let’s Encrypt.

After installing the letsencrypt package in Ubuntu, I ran the program with arguments to create a certificate for a website through a manual process:

letsencrypt certonly -a manual \
    -d tahirbutt.com -d www.tahirbutt.com \
    --config-dir ~/letsencrypt/config \
    --work-dir ~/letsencrypt/work \
    --logs-dir ~/letsencrypt/logs

The biggest headaches started when I fumbled a few times the manual checks the program requires to make sure that I actually administer the website I am attempting to certify. The manual process provides two consecutive challenge messages that specify the URLs the certification program expects exist with an expected output:

Make sure your web server displays the following content at
http://YOURDOMAIN.org/.well-known/acme-challenge/5TBu788fW0tQ5EOwZMdu1Gv3e9C33gxjV58hVtWTbDM
before continuing:

5TBu788fW0tQ5EOwZMdu1Gv3e9C33gxjV58hVtWTbDM.ewlbSYgvIxVOqiP1lD2zeDKWBGEZMRfO_4kJyLRP_4U

#
# output omitted
#

Press ENTER to continue

To create these URL endpoint on my Hugo site, I had to create two pages in the static folder (which are copied to the deploy folder during the Hugo build process) with the appropriate names and content, deploying the site between the two manual challenge steps:

mkdir -p static/.well-known/acme-challenge/
echo {Challenge message #1} > static/.well-known/acme-challenge/{endpoint #1}
git add static/
git commit -m "First LetsEncrypt challenge"
git push
echo {Challenge message #2} > static/.well-known/acme-challenge/{endpoint #2}
git add static/
git commit -m "Second LetsEncrypt challenge"
git push

Once these new pages were deployed on GitLab, I completed the letsencrypt manual process. Finally, I removed the custom domains from my GitLab Pages repository and recreated them with the contents of ~/letsencrypt/config/live/tahirbutt.com/fullchain.pem and ~/letsencrypt/config/live/tahirbutt.com/privkey.pem.

Voilà, voilà, https://tahirbutt.com was finally live!