Emacs, Hugo, GitLab and this Blog

EDIT - 2022-06-25: I got some notes on Reddit by the ox-hugo dev (u/kaushalmodi) regarding some information in this post.

I would be remiss of course if i didn’t mention theme here. See the thread on Reddit.


Quite simple really. I want to write my blog and publish it without leaving Emacs.

This post is my documentation – for me and whoever might be interested – of how I go about achieving this. Read on!


Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.

–– Hugo devs

Now… I really cannot say how my choice fell on Hugo back then. I have a strong suspicion that it might be the hipe back then. However, I haven’t looked back since. It is easy to use, fast enough for my needs and I have found a theme – Cactus theme by Zeran Wu – that tickles my fancy a whole lot.

I do really mean “easy to use” when I say it, which is why I will not go into the Hugo setup and the How-To itself. Just hop over to the Quick Start guide and find out for yourself.


I may have left out some details saying, that I want to write my posts inside Emacs. What I more precisely meant is that I want to write them in Org mode.

Lucky for me Kaushal Modi already wrote a package for this…

ox-hugo is an Org exporter backend that exports Org to Hugo-compatible Markdown (Blackfriday) and also generates the front-matter (in TOML or YAML format).

–– ox-hugo devs

Essentially you write your posts in Org Mode format and ox-hugo exports these to markdown in a manner “digestible” by Hugo.

Now of course there some rules you need to follow. The first one being selecting either the “One post per Org subtree” or the “One post per Org file” strategy.

I use the later. For some reason the first option did not work for me. In any case I prefer the second method. I tend to be able to keep track of the posts better this way.

Once you selected your strategy, you have now to create the directory, where your Org Mode files will live. This is called content-org by default and has to be in the root directory of the Hugo site.

You need to make sure to use some special ox-hugo properties as org-meta-line as well. This will translate largely into the front-matter for the markdown files exported. I my case the “header” for each post looks like this example of the current post:

# default Org Mode header:
#+TITLE: Emacs, Hugo, GitLab and this Blog
#+AUTHOR: Aimé Bertrand
#+DATE: [2022-06-23 Thu]
#+STARTUP: indent showall

# Properties, special to Hugo:
#+HUGO_TAGS: emacs hugo ox-hugo gitlab ci/cd blog
#+EXPORT_FILE_NAME: emacs-hugo-gitlab-blog

Most of these are self-explanatory. However…

The root directory of your site
This is the section (directory) inside of your Hugo content directory

It is helpful also to setup “Auto-export on Saving”. This way you can live preview your post using the Hugo server. I enable this per org file by adding the following at the bottom of the file. With an added benefit of being able to use other Emacs (Lisp) variables within this syntax.

* Footnotes
* COMMENT Local Variables                          :ARCHIVE:
# Local Variables:
# eval: (org-hugo-auto-export-mode)
# eval: (flyspell-mode)
# End:

Now this was just the steps I take to get my Hugo stack to work. There is a bunch more options to bend your posts to your liking. Head over to the ox-hugo website and find out more.

Org Mode

Now all the remains to do is really just write down your post in good old Org Mode.


It is almost needless to say, that the blog is under version control…

… Good, now that I just said it anyways, let’s talk about how I leverage this.

I use GitLab as a remote for my git controlled projects. Meaning of course that i can take advantage of this for publishing my posts. This is handled by a GitLab CI/CD Pipeline.

The Get started with GitLab CI/CD page is a good start to understanding the Ins and Outs of setting up your own pipeline. I suggest reading up a bit in case you want/need to know more.

Now to my pipeline…

Docker image

I use GitLabs own registry that has a Hugo Docker image to start with. For my case hugo:latest, which is minimal image based on alpine is more than good enough.

CI/CD variables

Since I upload the content to my site via SFTP, I need to store the following values as CI/CD variables.

The address of the SFTP server
The username of the SFTP server
The password to the username of the SFTP server
The Docker container needs to know and accept the ssh host key sftp server


I use the cli program LFTP, which can handle SFTP connections to upload the Hugo results.


All of the above results into the following .gitlab-ci.yml for the pipeline.

image: registry.gitlab.com/pages/hugo:latest

  # this is because the Hugo theme comes in a submodule

  # Set sane timeout
  timeout: 5 minutes
    # Install utils
    - apk add lftp openssh-client
    # Add the key and known hosts
    - mkdir /root/.ssh
    - touch /root/.ssh/known_hosts
    - echo $FTP_HOST_KEY > /root/.ssh/known_hosts
    - eval "$(ssh-agent -s)"
    # Build the website
    - hugo
    # Build the website
    - lftp --user $FTP_USER --password $FTP_PASS sftp://$FTP_HOST -e "set ftp:ssl-allow yes; mirror --reverse --verbose public/ ./; bye"
  only: # Only run on main branch


As publishing stacks go, mine is really not particularly sophisticated. However it works fantastically.

With a reasonable overhead I now write all of my post inside of Org Mode. I push my commits with Magit and my blog is automagically updated.