Automate hugo

Last Update:

Author: Christoph Stoettner
Read in about 4 min · 731 words

Fountain pen and a notebook

Photo by Aaron Burden | Unsplash

Mid 2018 I switched my blog from Wordpress to Hugo . Main reason was performance and that I can use Asciidoctor to write the posts.

What happened the last 18 months? I stayed with the theme I selected 2018, but I tweaked it a little bit. So I added lunr to implement searching, changed all scripts and fonts from CDN to local (privacy and tracking), updated Bootstrap 3 to 4.

Working with Bootstrap was quite fun, I haven’t done a lot of HTML or CSS the last years, but the grid and css classes from Bootstrap are working without checking each change on all browsers and are responsive.

Daily journal

After reading The Unicorn Project I started writing a daily journal with things I learned or figured out during the day. I tried this several times the last years, but never was happy with the result. This time I just use the same setup as my blog (Hugo, Theme and Asciidoctor), but deploy it to a local Docker container, which is started with podman.

Gitlab CI/CD

Hugo generates static html pages from Asciidoctor or Markdown sources. The first months I just ran the hugo binary on my local machine and did a rsync of the generated html to my blog and the container.

Updating Hugo, or reinstall my Notebook sometimes delayed a blog post. So I decided to automated the process through Gitlab CI/CD.


I added the theme (which is stored as a seperate git repository) as a submodule (git add submodule …​) to the blog content. .gitmodules can handle complete repository URL, or relative ones. The relative URL are easier to checkout in the pipeline, so I changed the file to


[submodule "themes/stoeps-theme"]
    path: themes/stoeps-theme
    url: ../stoeps-theme.git 
  • Edit Url and replace with the relative path (I have the theme in the same git group)

Creating .gitlab-ci.yml


  - build
  - deploy

  stage: build
  image: "node:alpine"
    - 'which curl || ( apk update && apk add curl)'  
    - 'which gem || (apk add ruby)'
    - curl -L | tar -xz && mv hugo /usr/local/bin/hugo
    - npm install
    - gem install asciidoctor
    - npx gulp build                  
      - master
      - $BUILDHTML
      - content/**/*
      - docs                          
    expire_in: 2 hours

  image: "debian:buster"
  stage: deploy
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - 'which rsync || ( apt-get update -y && apt-get install rsync -y )'
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | base64 --decode | tr -d '\r' | ssh-add - > /dev/null  
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -t rsa >> ~/.ssh/known_hosts
    - rsync -az docs/ --delete
      - master
      - content/**/*
  • Clone submodules with the main repository

  • Two stages, one builds html, one deploys the code

  • Check if curl is included in the container, if not install it

  • Run build with Gulp (see below)

  • Stage runs only for master-Branch, if variable BUILDHTML is set and changed files are in the content-tree

  • Keep directory docs as artifacts for the next stage

  • I created a seperate SSH key just for the synchronisation, it is stored as a variable in the Gitlab project

Store SSH-Key as variable

Create Key


base64 encode the key and copy to clipboard

base64 -i ~/.ssh/id_gitlab |tr -d '\n' | xclip

Store as variable

Gitlab Crontab setting

Syntax is equal to Linux cron, so the setting of this image will run the pipeline at 23:55 UTC every day. Other option could be hourly at 5 minutes past the full hour: 5 * * * *

I added a scheduler which runs hourly and sets the variables BUILDHTML and DEPLYBLOG, so I can check-in code to gitlab multiple times and the posts will only be build on a scheduled basis.

Just set the variables to true.

Gitlab Scheduler variables


The article Continuously Deploy a Hugo Site with GitLab CI added Gulp to minify the built HTML. Cool idea and saves me some megabytes. I added css-minify and build the hugo page with:

npx gulp build


var gulp: require("gulp");
var htmlmin: require("gulp-htmlmin");
var cssmin: require("gulp-cssmin");
var shell: require("gulp-shell");

gulp.task("hugo-build", shell.task(["hugo"]));

gulp.task("minify-html", () => {
    return gulp.src(["docs/**/*.html"])
        collapseWhitespace: true,
        minifyCSS: true,
        minifyJS: true,
        removeComments: true,
        useShortDoctype: true,

gulp.task('minify-css', () => {
    return gulp.src(["docs/**/*.css"])

gulp.task("build", gulp.series("hugo-build", "minify-html", "minify-css"));
Add a comment
There was an error sending your comment, please try again.
Thank you!
Your comment has been submitted and will be published once it has been approved.

Your email address will not be published. Required fields are marked with *

Suggested Reading
Aaron Burden: Fountain pen and a notebook

I switched my blog to Hugo the last days. After nearly 12 years with WordPress , I needed something new. Why did I drop WordPress, one of the most used blog engines in the world?

Most used means always most interesting for bad guys. Dynamic pages are slower and can contain more vulnerabilities than static pages (which Hugo generates). Hugo supports git, so I have version control in my posts and design. I can start a small web server locally and test the posts: hugo server -D and the most convenient thing: I can use VIM for editing.

Last Update:
Read in about 3 min
Card image cap

In late 2021 I had an HCL Connections environment starting swapping, because the AppCluster used more than 30 GB of memory.

The system has

  • two nodes
  • is installed with the medium-sized deployment option
  • About 7500 users with a high adoption rate, because Connections is also used as intranet
Last Update:
Read in about 5 min
Card image cap

Here for example embedding a video to a blog post. Prerequist is that you’ve no fear to change some html source.

Last Update:
Read in about 1 min