Automating Hugo build with GitLab CI
I found a ton of posts that describe how to use Hugo and GitLab CI to create and host a static site on GitLab Pages.
However, I had a different requirement: I want to use a local GitLab CE instance to store my content, use GitLab CI (locally) to build the static output and then push that output to GitHub to publish on GitHub Pages.
- spin up a Docker container
- check out the latest GitHub Pages content commit from GitHub
- run Hugo on the latest source content commit from GitLab CE
- if the content has been updated, push the changes to GitHub
The Docker container
I tried a bunch of the existing Hugo containers, but none of them had all the components required (hugo+git+openssh) to achieve my goal. So, I shaved a few yaks and created my own hugo-builder image that has all the bits I need.
In order to check out content (and later push it back) to GitHub, I need to be able to use SSH and have a keypair configured.
Using GitLab Secret Variables
I recommend creating a specific keypair for your GitLab CI instance so that it is kept separate from your main SSH keypair. This is required to pull and push from GitHub.
Also, I don’t want to store the private key anywhere visible or in a repo, so
I’m using GitLab Secret Variables
instead. Create a variable named
PRIVATE_KEY and store the content of
in that variable.
I also created a
GITHUB_KEY variable that stores the public key of GitHub so I
can automatically add it to
~/.ssh/authorized_keys in the build container.
I had a bunch of problems with GitLab CI not properly pulling the Hugo theme I use
which is configured as a git submodule. So, I do it manually as part of the
process in the CI pipeline.
Hugo stores its static output in the
public/ folder. This folder is included in
.gitignore for the source content so it is never stored on the local GitLab
GitLab CI automatically checks out the latest commit from the repo when it starts
a build, so before I run Hugo, I pull the current content from GitHub to create the
git clone email@example.com:username/username.github.io.git public
Setup and clone from GitLab is done in the
Then, when Hugo runs, it will update files in that same location, so I can use git
to check if the content has actually changed. This is done using the
image: avmiller/hugo-builder build: stage: build before_script: - git submodule update --init - mkdir -p ~/.ssh - echo "$PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa - echo "$GITHUB_KEY" | tr -d '\r' > ~/.ssh/known_hosts - git config --global user.name "Your Name" && git config --global user.email firstname.lastname@example.org - git clone email@example.com:username/username.github.io.git public script: - hugo after_script: - export GIT_LOG_MESSAGE=`git log --format=%B -n 1 $CI_BUILD_REF` - cd public - if ! git diff --no-ext-diff --quiet --exit-code; then git add --all && git commit -sam "$GIT_LOG_MESSAGE" && git push -u origin master; fi only: - master
If all goes well, this will be the first new post that is actually published using this automated workflow.
Obviously, if it doesn’t, I’ll have to fix it and republish this page and you won’t know anyway. Magic!