Building a website with Hugo and GitLab Pages

Introduction Link to heading

This site has been built with Hugo and the hugo-coder theme, and is hosted on GitLab Pages. This post has some notes on these tools.

About Hugo Link to heading

Hugo is a “static site generator”, which means that it prepares pages in advance of serving them. This is in contrast to a “dynamic site generator”, which generates pages “on the fly” for every single page request. Serving static pages has many advantages, particularly speed, and there isn’t really any particular need to generate content such as this dynamically anyway.

Content is prepared in Markdown, and the templates which change how a site looks are called Hugo Themes. Separating content from presentation in this way has many advantages, e.g. you can (at least in theory) redesign your site without having to change any content, or present the same content differently to different devices, or even potentially move to a different static site generator without having to rewrite your content.

There are alternatives to Hugo such as Jekyll. I chose Hugo because it sounded good, and I haven’t had any issues with it so I’ve stuck with it.

About GitLab Link to heading

GitLab is a repository hosting service for the git distributed version-control system. Using the git version-control system to manage your content and other site assets is an advantage for software developers because git is a tool they’re likely to be familiar with already. GitLab Pages provides free hosting for static content, such as content generated via a static site generator like Hugo.

The Markdown files are stored in a content folder, and changes are pushed to the repository. GitLab also has a continuous integration pipeline which is triggered by a push, and this builds the site and copies it to GitLab Pages, from where it is served.

There are alternatives to GitLab Pages such as GitHub Pages. I chose GitLab because it offered private repositories and continuous integration, although GitHub now has these too.

git basics Link to heading

Installing git and configuring ssh Link to heading

On Ubuntu:

sudo apt install git

Username and email are configured via:

git config --global "<username>"
git config --global "<email>"

Connectivity is easier if ssh is set up. This means that instead of entering commands like git clone<repositorypath>, which will then prompt you for username and password, you’ll use git clone<repositorypath>, and not be prompted for username and password. To set this up, first generate a key, e.g. via:

ssh-keygen -t ed25519

Then select location (e.g. default at /home/<username>/.ssh/id_ed25519) and passphrase if you want. Finally, as per Use SSH keys to communicate with GitLab, copy this to your GitLab account via Settings / SSH keys, and test via:

ssh -T

Setting up the git repo Link to heading

To setup git on your local machine with a new (empty) repository from GitLab:

  • Create the GitLab project you want to save it in via the GitLab web interface, via New project and Create blank project. You will need to enter a name for the project, e.g. <sitename>. I’ll use <repositorypath> to refer to the <username>/<sitename> combination.
  • Clone the project locally.
cd /home/<username>/
git clone<repositorypath>
cd <sitename>
git add .
git commit -m "Initial commit"
git push -u origin master

If you find you’ve uploaded files that you don’t need or want in version control, add their details to .gitignore (which is normally part of the repo and so potentially visible to others) or .git/info/exclude (which is not part of the repo and so not visible to others) so they aren’t subsequently readded to git, then remove from git via:

git rm --cached <filename>

and then commit and push. The files will won’t be deleted from the local file system.

To add files:

git add <filename>

and then commit and push.

Hugo basics Link to heading

This is a summary of the Hugo Quick Start.

Installing Hugo Link to heading

Download and install the latest release from Hugo Releases. I found I needed to install Hugo extended, i.e. hugo_extended_<version>_linux-amd64.deb, to avoid TOCSS errors with some themes such as hugo-coder. If Hugo is already installed and you want to update it, simply running sudo dpkg -i hugo_extended_<version>_linux-amd64.deb will upgrade.

Setting up a new site Link to heading

To create the new site outside the git repo setup above:

cd /home/<username>/
hugo new site <tempname>

If you try to create it at the location of the <sitename> above you will get “Error … already exists and is not empty”, so I’m using a <tempname> instead. To move the new site from its temporary location into the git repo created above:

mv <tempname>/archetypes/ <sitename>
mv <tempname>/config.toml <sitename>
mv <tempname>/content/ <sitename>
mv <tempname>/data/ <sitename>
mv <tempname>/static/ <sitename>
mv <tempname>/themes/ <sitename>
rm -r <tempname>

Adding and managing themes Link to heading

And add a theme, e.g. the Hugo Coder theme:

cd <sitename>
git submodule add themes/hugo-coder
echo 'theme = "hugo-coder"' >> config.toml

Note that if adding other themes, some theme documentation suggests a git clone<repositorypath>, but if you are already inside a git project it is better to use a git submodule add<repositorypath> themes/<name>. Submodules can be updated at a later date via git submodule foreach git pull origin master, or git submodule foreach git pull origin main if it has switched from master to main, noting that customisations in layouts/ may need to be redone if the source files change. If you want to remove a submodule, use git rm -f themes/<name> (although if you later want to readd that submodule you will need to remove references to it in .git/config and remove it from .git/modules/themes/ before you can execute the git submodule add again).

Creating and viewing a post Link to heading

To create your first post:

hugo new posts/

And edit accordingly.

Note that the Markdown files will typically have some metadata at the start of the file, known as the “front matter”, e.g.

title: "Building a website with Hugo and GitLab Pages"
date: "2019-11-08"
draft: false

To view, start the local dev server:

hugo server -D

And view on http://localhost:1313/ .

Automatically building and deploying your site, and making it public Link to heading

Configuring continuous integration Link to heading

As per Host on GitLab, change to the site location, touch .gitlab-ci.yml and edit accordingly.

Note that I am using as the image, rather than hugo:latest, to avoid possible TOCSS errors in the build, as per the Installing Hugo section above.

I’ve added /public and /resources to .gitignore because these are system generated and so there’s no benefit to managing in git:

echo "/public" >> .gitignore
echo "/resources" >> .gitignore
git add .
git commit -m "Added /public and /resources"

Making your site publicly available Link to heading

To push and trigger the site build and deploy:

git push -u origin master

As per earlier comment, if you have set up ssh and the remote origin<repositorypath>, then this will not require a username and password.

The build status will show in your project pipeline, and once successfully built, assuming no errors, you should see at https://<username><sitename>/.

Setting up a custom domain Link to heading

Configuring DNS Link to heading

In my case I wanted to point an existing domain to the Hugo site. GitLab has some documentation on how to Add your custom domain. In my case, I made the following main changes:

  • I went to my domain provider and added the A record to

  • In GitLab, I went to Settings / Pages, and added the domain.

  • Back in the domain provider interface I need to add the TXT record to verify the domain.

  • Once verified, I enabled SSL, and it sorted the certificate from Let’s Encrypt automatically, and then I selected “Force HTTPS”.

  • In GitLab, in Settings / General / “Visibility, project features, permissions” for the project, I made Pages visible to Everyone.

Configuring and customising Hugo themes Link to heading

I experimented with a few themes, before settling on Hugo Coder. There are however a few configurations and customisations I’ve performed.

Configuration changes are simply performed via config.toml.

Customisations are performed via template changes, which are saved in files in layouts which override the equivalents in themes. You would normally start by copying the original files from themes to layouts and then performing the customisation, e.g. copying themes/hugo-coder/layouts/partials/home.html to layouts/partials/home.html. If you subsequently update the theme, as per the process in the Adding and managing themes section above, you may then need to recopy the original files from the theme and reapply any customisations. If you change theme you will need to remove the custom layout.

The first configuration was to add a link to my Hacker News profile.

Icons seem to come from Font Awesome and I think you can pretty much add any icon from there with the right identifier along with any link in the [[]] section of config.toml, e.g.:

	name = "Hacker News"
	icon = "fab fa-hacker-news"
	weight = 1
	url = "<userid>"

Adding a Table of Contents (toc) to the start of every page Link to heading

I like a Table of Contents (toc) at the start of every page. There is a reference to how to do this on the Table of Contents . What I did was copy themes/hugo-coder/layouts/posts/single.html to layouts/posts/single.html and add {{ .TableOfContents }} just before {{ .Content }}.

Removing new lines from the titles Link to heading

I don’t like the new lines within the <title> tags. To remove these, in layouts/posts/single.html change:

  {{ .Title }} · {{ .Site.Title }}


  {{- .Title }} · {{ .Site.Title -}}

Adding last modified date Link to heading

I also like something on each page itself to indicate whether the content has been revised or not. That requires a lastmod field in the front matter, and the following in the layouts/posts/single.html just below the <\time>:

{{ $date := .Date.Format .Site.Params.dateFormat }}
{{ $lastmod := .Lastmod.Format .Site.Params.dateFormat }}
{{ if ne $lastmod $date }}, <span class="date-info italic"> revised {{ .Lastmod.Format .Site.Params.dateFormat }}</span>
{{ end }}

Adding IndieWeb support Link to heading

I have an h-card and so I can login with IndieAuth. To do this:

  1. Add a relme to the [Params] in config.toml, so the value can be configured rather than hardcoded.
  2. Copy themes/hugo-coder/layouts/partials/home.html to layouts/partials/home.html, and themes/hugo-coder/layouts/partials/home/author.html to layouts/partials/home/author.html. Note comments above about updating this if updating the theme source.
  3. In layouts/partials/home.html make the following change:


  <div class="about">


  <div class="about vcard h-card">
  1. In layouts/partials/home/author.html, change:
<h1>{{ }}</h1>


<h1 class="fn">{{ }}</h1>

And add the following to the end:

<a href="{{ .Permalink }}" rel="me" class="u-url u-uid"></a>
{{ if .Site.Params.relme }}
<link rel="me" href="{{ .Site.Params.relme }}" />
{{ end }}
<link rel="authorization_endpoint" href="">

I like something on the home page to indicate how often the site is being updated, so people can see at a glance whether it is actively maintained or dead. I use a Featured posts box for that purpose, marking the posts I want to appear with a:

featured: true

in the front matter. To show these on the home page, the following needs to be added to the end of layouts/partials/home.html:

<section class="container list">
  <h3 class="centered">Most popular posts</h3>
    {{ range (.Paginator).Pages  }}
	{{ if .Params.featured }}
	      <span class="date">{{ .Date.Format (.Site.Params.dateFormat | default "January 2, 2006" ) }}</span>
	      <a class="title" href="{{ .Params.ExternalLink | default .RelPermalink }}">{{ .Title }}</a>
	{{ end }}
    {{ end }}

Adding a search page Link to heading

This is a new file in layouts/search/single.html, with the contents as described at, and a link to the search page in the config.toml.

Normal workflow for making edits to the site Link to heading

Once the site is all set up, installed and running, the normal workflow is to:

  1. Pull latest remote changes:
git pull origin master

Note that this step is only necessary if it is a shared project (i.e. other people potentially working on it), or if you have been working on it on multiple devices.

  1. Start the local web server:
hugo server -D

And view the site at http://localhost:1313/ .

  1. Edit MarkDown files locally. When you save the files the changes will reload on your browser automatically, which is nice.

  2. Once you’re ready to publish, change draft: true to draft: false in the Markdown if necessary, and do the usual git add, commit and push:

git add .
git commit -m "Description of change"
git push -u origin master
  1. Check on the published web site. You may need to refresh your browser since static content is typically cached.

Editing and publishing your site on a mobile phone Link to heading

On an Android device, install termux, and in the terminal:

apt update
apt upgrade
apt install openssh
apt install git
git config --global "<username>"
git config --global "<email>"
cd storage/shared
mkdir projects
cd projects
git clone<repositorypath>

On my device, storage/shared is /data/data/com.termux/files/home/storage/shared which is a symlink to /storage/emulated/0/shared, so you can find the files in there. Then you can use your usual editing and publishing workflow (minus the previewing), i.e. before starting an editing session ensure the local files are up-to-date with git pull origin master, edit with your Android text editor (I’m currently using Markor), and publish with git add ., git commit -m "Comment", and git push -u origin master.

Its a bit awkward from the command line, but you could make it slightly easier by setting up ssh similar to the desktop process, and there are other more integrated tools although I can’t vouch for them. At least it works fine for quick edits you don’t need to preview.

Conclusion Link to heading

So there it is. Not hugely complex, although admittedly not something I’d recommend a non-technical person try.

Oh, and source is at GitLab Michael I Lewis web site.