Hugo & GitHub Actions workflow

Developing my website

I use Hugo to generate my website. I write my templates, stylesheets and scripts with VS Code and thanks to the hugo server command, I can see the results instantly on my Internet browser at localhost:1313.

When I’m satisfied, I use Git to commit my changes. I keep track of every single line of code that I created, modified or deleted at any time. This source control repository lives on my computer and integrates perfectly with VS Code & its GitLens extension. Even for a small, single-user project like a personal page, it’s super fun to see it evolve and being able to look back in time or visualize heatmaps of my edits.

Setting up my live website

Now I COULD run the hugo command in my terminal and Hugo COULD generate ‘Internet-ready’ files in a folder on my computer. Then I COULD initialize a new Git repository out of it and I COULD push that content online every time that I wanted to edit something, but please no.

I certainly don’t want to deal manually with the ‘Internet-ready’ stuff. They’re the standalone HTML files now stripped of all of Hugo’s logic and functions. They’re the different sizes thumbnails of my processed images. They’re the transpiled, concatanated and minified CSS and JS … They’re Hugostein’s monster, a necessary evil to display static pages properly on everyone’s browser as fast as possible.

I often make changes that don’t even result in anything different regarding the public website, but they improve the way I do things on my end. So if I have to push something online related to this project, it has to be my development files. They are organized, human-readable and workable. Content like blog posts is written in plain text Markdown in a dedicated folder. The head of all my HTML layouts is a single partial, the navigation menu is another partial, the footer is another partial. CSS and JS can also be produced with whatever library I want to use.

On top of that, if those are accessible on GitHub then I could update them with my credentials from anywhere in a matter of seconds. When I come back to my desktop, I can just git pull on my own remote repository and continue where I left off.

Now I know what I need so here’s what I can do:

My subdomain (www) will point directly to the live branch and that’s what the public will see. GitHub Pages requires a CNAME file to be present at the root of the repository when using a custom domain name so I prep Hugo to generate this file on each future build.

OK so how can I push my development files to the main branch and have a normal website automatically come out of it in the live branch?

Enters GitHub Actions

GitHub Actions is a relatively recent feature offered by GitHub for both public and private repos. It helps automate workflow and it has a marketplace for published user-made actions.

First, I need to give permission to GitHub Actions to perform the job on my behalf otherwise it’s going to fail. This is done directly on the GitHub website. The permission is a token that GitHub can help me customize in Settings > Developer settings > Personal access tokens. Once I have one with my workflow permissions, I go to my Repository Settings > Secrets and add it there with the name LXP_TOKEN. This name will be referred to later but its token value will always stay hidden.

Then I create and push a YAML file .github/workflows/deploy_hugo.yml to my repo main branch. This is a series of jobs instructions. Because it’s YAML indentation matters.

# I call this action "Deploy Hugo".
name: Deploy Hugo

# It will be triggered everytime there's a push event on the main branch of
# my repo. It could also be triggered manually at any time on the repo Actions
# tab to test the action, or paused or disabled.
      - main

# Now I tell GitHub that it has a single multi-steps job to do called "deploy"
# (not a reserved keyword) and that it should perform it on the latest version
# of Linux Ubuntu.
    runs-on: ubuntu-latest

      # First I want GitHub to checkout on my repo so it can work with it. This
      # step uses a convenient built-in action named checkout@v2 found in the
      # marketplace.
      - name: Checkout
        uses: actions/checkout@v2

      # Then GitHub needs to setup the same Hugo environment as mine on its
      # Linux VM to obtain the same results as I do when I process my files.
      # Fortunately, a user-made action that does that exists and accepts
      # custom settings. I use Hugo Extended 0.82 because it comes with Sass.
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
          hugo-version: '0.82.0'
          extended: true

      # Then GitHub needs to generate my static pages by simply running the
      # hugo command in the shell.
      - name: Build Hugo
        run: hugo

      # Finally, GitHub needs to publish those to my 'live' branch. Because
      # it will now make actual changes to my repository, it needs its secret
      # token to do so. Also, force_ophan set to true means that every time
      # this step is performed, the commit history for the target branch will
      # be erased because I don't care about it.
      - name: Publish
        uses: peaceiris/actions-gh-pages@v3
          personal_token: ${{ secrets.LXP_TOKEN }}
          publish_branch: live
          force_orphan: true

As you can see, half those steps are other user-made actions. Thanks peaceiris!

This particular job took about 10 seconds to perform and GitHub shows exactly how long each step took. Setting up the VM takes 2s, the “Build Hugo” step takes ~350ms, etc. GitHub also gives access to verbose logs for each job ever performed.

This is important if you use a lot of actions or your build takes longer because there’s a quota of minutes each month on private repos, depending on your account. Free accounts get 2,000 minutes/month on private repos for example. This quota gets eaten 2x faster on Windows, and 10x faster on macOS, so it’s best to tell it to run your actions on Linux unless it can’t.