Lenon Oliveira

How to publish a blog on GitHub Pages with Hugo and GitHub Actions

I decided to start a new blog and, after looking at many of the available options for blogging, I chose a static site generator. I had used Jekyll in the past, but this time I wanted to try something new, so I gave Hugo a try. I also wanted to publish this blog to GitHub Pages whenever I push new content to the GitHub repo. So in this blog post I describe how I’m using it along with GitHub actions to automatically deploy this blog to GitHub Pages.

The first step is to declare a new workflow file for GitHub Actions. For this, I added a new file in .github/workflows/main.yml with the following content:

name: Publish
on: push
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true
          fetch-depth: 0

      - name: Build and publish
        run: ./publish.sh

This workflow is configured to run on every push to the repo (on: push). It uses actions/checkout configured to also fetch submodules (submodules: true) because Hugo themes are usually added as git submodules. The fetch-depth option is configured with 0 to make it get all history for all branches and to make possible to commit on the gh-pages branch.

Now the next step is to create the shell script referenced on the workflow file. This script will build the static site, commit any changes and push them to the repo. Here’s how I started the publish.sh file:

#!/usr/bin/env bash
set -euxo pipefail

The first line makes the shell search for bash in user’s PATH. It is more flexible than using a hardcoded path. The second line makes bash a bit safer:

I use these 2 lines in almost all shell scripts that I write. Now let’s see the actual script.

As the virtual environment for GitHub Actions runner includes Snap, I used it to install Hugo from the official package:

sudo snap install hugo

Next, I added some commands for debugging:

hugo env
hugo config

The first one prints Hugo version and environment info. Second one prints the site configuration. They are useful for troubleshooting.

Then I used the following commands to build the site into a temporary folder:

builddir="$RUNNER_TEMP/hugo-build"
mkdir -p "$builddir"
hugo --verbose --destination "$builddir"

To keep things simple, I decided to place static files in an orphan git branch. With this type of branch, I can simply publish files in the root folder and GitHub Pages will work right away. The only requirement is for the branch to be named gh-pages.

Here’s the command to create a new orphan branch or to switch to it when it already exists:

if ! git show-ref --verify --quiet refs/remotes/origin/gh-pages ; then
  git switch --recurse-submodules --orphan gh-pages
else
  git switch --recurse-submodules --create gh-pages --track origin/gh-pages
fi

As I mentioned, the conditional will check if gh-pages already exists and, if not, will create it as an orphan branch. Later on, the script will push this branch to the repo. Otherwise, when it is not the first run (gh-pages was already pushed), it will simply switch to it locally.

Now it is necessary to move static files to the repo. I used rsync for this task:

rsync --verbose \
      --archive \
      --delete \
      --exclude .git \
      --exclude CNAME \
      "$builddir/" .

This command will add new files and remove stale ones. It is important to not touch .git because it contains the actual git repo. CNAME should also not be touched because GitHub Pages uses it to store custom domains.

Now, the last step:

git config user.name github-actions
git config user.email github-actions@github.com
git add -- .
git commit --allow-empty --message "publish changes from commit $GITHUB_SHA"
git push origin gh-pages

I included the --allow-empty flag because sometimes a change in source files does not cause an actual change in the output of static files.

Let’s see how the final script looks like:

#!/usr/bin/env bash
set -euxo pipefail

sudo snap install hugo

hugo env
hugo config

builddir="$RUNNER_TEMP/hugo-build"
mkdir -p "$builddir"
hugo --verbose --destination "$builddir"

if ! git show-ref --verify --quiet refs/remotes/origin/gh-pages ; then
  git switch --recurse-submodules --orphan gh-pages
else
  git switch --recurse-submodules --create gh-pages --track origin/gh-pages
fi

rsync --verbose \
      --archive \
      --delete \
      --exclude .git \
      --exclude CNAME \
      "$builddir/" .

git config user.name github-actions
git config user.email github-actions@github.com
git add -- .
git commit --allow-empty --message "publish changes from commit $GITHUB_SHA"
git push origin gh-pages

You can see the up-to-date version of this script here on GitHub.

#hugo #blogging #github