Creating a blog using Hugo, GitLab pages, and a custom domain

Posted on Jan 18, 2024
tl;dr: This posts describes the steps I followed to set up a blog using Hugo and GitLab, with a custom domain.

Introduction

This post describes the steps I took to set up the present blog using Hugo, GitLab and a custom domain.

My main requirements were:

  • To avoid a blogging platforms (such as Medium or Substack),
  • A cost-effective solution, ideally free,
  • A low-maintenance solution, easy to work with and to update.
  • A well-established and well-documented solution, that is also still maintained.
  • Finally, the ability to use a custom domain.

The last criteria I wanted is basically the right to be wrong, i.e., to be able to change part of the solution in case of need. This is also why I chose not to use any blogging platform.

Tools

My requirements being pretty common, I went for a Static Site Generator:

  • Content is written using markdown files. It’s portable and can be read by humans.
  • The output of a SSG produces is a HTML/JS/CSS site, which uses few resources and can be deployed virtually anywhere.
  • A SCM such as git allows to keep track of changes.

I’ve heard about Hugo a few years ago, and I just went for it. I may consider alternatives such as Zola in the future.

For hosting, I evaluated the following options:

Using GitHub Pages or GitLab pages solution appeared for me the most practical solutions, because of git integration and their CI/CD feature. I finally chose GitLab, as the free tier supports public pages using a private repository.

Setup

The setup is quite straightforward, and involves following the Hugo Quick start and the GitLab Pages documentation.

Installing Hugo

Installing Hugo can be done using various methods, I just picked the easiest one for me from the documentation, using winget:

winget install Hugo.Hugo.Extended

Create a git repository

Creating a git repository can be done either by initializing one locally then adding the remote origin:

git init
git remote add origin https://gitlab.com/jallal_brahimi/blog.git
git branch -M main
git push -uf origin main

Or by creating directly a remote repository with the GitLab web interface, and cloning it locally:

git clone https://gitlab.com/jallal_brahimi/blog.git

I later found that a Hugo template can be prepopulated from GitLab directly.

Finally, I’ve added a .gitignore file to exclude files created by Hugo that git should ignore :

/public/
/resources/_gen/
/assets/jsconfig.json
hugo.log
hugo_stats.json

.hugo_build.lock

hugo.exe
hugo.darwin
hugo.linux

Create a Hugo website

Once the repository is set up, generating an empty web site is straightforward:

# Init a new Hugo project in blog folder
hugo new site blog
cd blog

# Specify a theme. it can be changed later
`git` submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
echo "theme = 'ananke'" >> hugo.toml

# Add a new post
mkdir posts/blog-setup/
hugo new content posts/blog-setup/index.md

Choose a theme

Various themes are available for Hugo, showcased on the official website.

I wanted something minimalistic with a dark mode and eventually chose Archie](https://themes.gohugo.io/themes/archie/) over Bear Cub.

If a theme has already been selected, it can be removed

git rm themes/ananke/
rm -rf .git/modules/themes/ananke/

To set the new theme:

`git` submodule add https://github.com/athul/archie.git themes/archie
echo "theme = 'archie'" >> hugo.toml

Theme configuration is done by editing the hugo.toml file:

baseURL = 'https://www.jallalbrahimi.com/'
languageCode = 'en-us'
title = "Jallal's blog"
theme = 'archie'
copyright = "© Jallal" # (CC BY-NC-SA 4.0)

pygmentsstyle = "monokai"
pygmentscodefences = true
pygmentscodefencesguesssyntax = true

# Generate a nice robots.txt for SEO
enableRobotsTXT = true

[params]
 mode="dark"
 useCDN=false
# subtitle = "Minimal and Clean [blog theme for Hugo](https://github.com/athul/archie)"

[[params.social]]
name = "Email"
icon = "mail"
url = "mailto:xxxx"

[[params.social]]
name = "LinkedIn"
icon = "linkedin"
url = "https://www.linkedin.com/in/jallal-brahimi-62abb224/"

[[menu.main]]
name = "Home"
url = "/"
weight = 1

[[menu.main]]
name = "All posts"
url = "/posts"
weight = 2

[[menu.main]]
name = "About"
url = "/about"
weight = 3

[[menu.main]]
name = "Tags"
url = "/tags"
weight = 4

Setup the CI/CD

Hugo documentation provides a script for publishing on commit.

I’ve updated the .gitlab-ci.yml so that the latest version of Hugo is used. I’ve also added a new step to force the fetch of git submodules, as this stopped working when I’ve changed the theme.

variables:
  DART_SASS_VERSION: 1.70.0
  HUGO_VERSION: 0.122.0
  NODE_VERSION: 20.x
  GIT_DEPTH: 0
  GIT_STRATEGY: clone
  GIT_SUBMODULE_STRATEGY: recursive
  TZ: Europe/Brussels

image:
  name: golang:1.20.6-bookworm

before_script:
  - `git` submodule update --init --recursive

pages:
  script:
    # Install brotli
    - apt-get update
    - apt-get install -y brotli
    # Install Dart Sass
    - curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
    - tar -xf dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
    - cp -r dart-sass/ /usr/local/bin
    - rm -rf dart-sass*
    - export PATH=/usr/local/bin/dart-sass:$PATH
    # Install Hugo
    - curl -LJO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb
    - apt-get install -y ./hugo_extended_${HUGO_VERSION}_linux-amd64.deb
    - rm hugo_extended_${HUGO_VERSION}_linux-amd64.deb
    # Install Node.js
    - curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION} | bash -
    - apt-get install -y nodejs
    # Install Node.js dependencies
    - "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
    # Build
    - hugo --gc --minify
    # Compress
    - find public -type f -regex '.*\.\(css\|html\|js\|txt\|xml\)$' -exec gzip -f -k {} \;
    - find public -type f -regex '.*\.\(css\|html\|js\|txt\|xml\)$' -exec brotli -f -k {} \;
  artifacts:
    paths:
      - public
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Setup a custom domain

GitLab describes how to use a custom domain with Gitlab Pages in their documentation

First, a new domain/subdomain need to be created: GitLab Pages GitLab Domain

Gitlab provides then a key to identify the website. This key must be added as a TXT entry in the DNS configuration (using the registrar UI) to confirm the domain’s ownership and allow GitLab to redirect requests to the relevant website.

DNS Configuration

Gitlab suggests to use an ALIAS entry for the DNS configuration, but some registrars do not support this keyword. I’ve first considered to add a CNAME entry, but it turned out that the specification of DNS prevents to have A TXT and CNAME simultaneously.

In the end, I’ve just added an A entry pointing to the 35.185.44.232. Using the TXT entry, GitLab is able to redirect any query to the correct website.

I performed this configuration on the www subdomain, but the same applies to the root domain. Another option can be - if the root domain is hosted - to use a .htaccess file to redirect to the www subdomain.

A nice perk with GitLab is that a Let’s Encrypt certificate can be generated and configured automatically.

Usage

# To create a new post
hugo new content posts/my-post/index.md

# To launch the hugo server in draft mode
hugo server -D