DEV Community

Robin Moffatt
Robin Moffatt

Posted on • Originally published at rmoff.net on

Scheduling Hugo Builds on GitHub pages with GitHub Actions

Over the years I’ve used various blogging platforms; after a brief dalliance with Blogger I started for real with the near-inevitable Wordpress.com. From there I decided it would be fun to self-host using Ghost, and then almost exactly two years ago to the day decided it definitely was not fun to spend time patching and upgrading my blog platform instead of writing blog articles, so headed over to my current platform of choice: Hugo hosted on GitHub pages. This has worked extremely well for me during that time, doing everything I want from it until recently.

As a static-site generator, Hugo supports the idea of future-dated posts, but you still need to regenerate that static site once the date has arrived. It’s not the same as serving dynamic content on a blog in which you simply say IF blogDate ⇒ NOW(). With a static site you publish content by building it and pushing those static files to the server (GitHub Pages in my case) - and if you’ve published content that’s future dated, you need to find a way to trigger that publishing process.

I recently undertook a little project to create and publish twelve videos in the run-up to Christmas; three of them were to be published after I’d hung up my laptop for the year and would be comfortably ensconced on a sofa and up to my ears in Quality Street and mince pies. Whilst there were plenty of tools to publish tweets on a schedule, and YouTube can schedule the publication of a video, my blog (from which the videos were linked) looked like it was going to be a bit of a problem. A bash while loop running on my laptop, or even a crontab, seemed a bit hacky and ultimately not reliable enough.

The answer turned out to be this thing called GitHub Actions which, like a lot of technology these days, I’d heard of and was vaguely aware of—but had no idea what it actually did.

GitHub Actions lets you take actions (duh!) based on the code in your repo on GitHub. Since my blog is just a repo, it can be set to trigger an action every time I push a new article to it (like this one), or indeed on a schedule also.

I found a few articles to use, primarily this nice one from Gunnar Morling.

Preparation

See my previous article for details of how I’ve installed and configured Hugo.

I’ve got two repositories:

  • https://github.com/rmoff/rmoff-blog/ - holds the source code for my blog

  • https://github.com/rmoff/rmoff.github.io - the static site served through GitHub Pages. I have a custom domain (rmoff.net) set on this

Repository secret ( source repository)

In the source repository, from which the GitHub Action will run, you need to create a Repository secret. This is the authorisation under which the Action will run.

I’ve seen tutorials do this with both SSH keypairs, and with GitHub Personal Access Tokens. Based on Gunnar’s article, I used the SSH keypair approach, generating a unique pair just for this purpose:

ssh-keygen -t rsa -b 4096 -C "$(git config user.email)" -f gh-pages -N ""
Enter fullscreen mode Exit fullscreen mode

In the repository secret for your source repository (so https://github.com/rmoff/rmoff-blog/ in my case) put the private half of the key (if you use the above command it’ll be called gh-pages). Give it a name and make a note of that name. I used ACTIONS_DEPLOY_KEY.

gh1

NOTE: You configure this secret against the source repository - I wasted time in my GitHub profile settings looking for an option that wasn’t there 🤦‍♂️

Deploy key ( target repository)

The deploy key is configured against the repository to which your Action is going to push the static site content, which is https://github.com/rmoff/rmoff.github.io in my case. If you are using SSH keys then it is the public part of your keypair that you generated, and using the above code will be called gh-pages.pub.

gh2

NOTE: You configure this deploy key against the target repository that holds your static site - I wasted time in my GitHub profile settings looking for an option that wasn’t there 🤦‍♂️

Configuring the Action

Once you’ve set up the auth per above, you need to configure the action itself. This is done through a YAML file that you put in the source repository from which you want it to run. Mine is based heavily on the one in Gunnar’s article, with a few tweaks.

name: GitHub Pages

on:
  push:
    branches:
     - master
  schedule:
    # Run every ten minutes
    - cron: '*/10 * * * *'

jobs:
  build-deploy:
    runs-on: ubuntu-18.04
    steps:
    - uses: actions/checkout@v1                  

    - name: Install Ruby Dev                     
      run: sudo apt-get install ruby-dev

    - name: Install AsciiDoctor and Rouge
      run: sudo gem install asciidoctor rouge

    - name: Setup Hugo                           
      uses: peaceiris/actions-hugo@v2
      with:
        hugo-version: '0.75.1'

    - name: Build                                
      run: hugo

    - name: Deploy                               
      uses: peaceiris/actions-gh-pages@v3
      with:
        deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
        external_repository: rmoff/rmoff.github.io
        publish_branch: master
        # Without `keep_files` the `CNAME` file in the target repo will get removed
        # and the custom domain configured for GitHub pages will get dropped every 
        # time the action runs…
        keep_files: true
Enter fullscreen mode Exit fullscreen mode

Points to note:

  • The action will trigger every ten minutes:
    schedule:
      # Run every ten minutes
      - cron: '*/10 * * * *'
Enter fullscreen mode Exit fullscreen mode

as well as when a push is made to the master branch:

    push:
      branches:
      - master
Enter fullscreen mode Exit fullscreen mode
  • You can tie the Hugo version to a particular number; I’ve used the same one as I run locally
          hugo-version: '0.75.1'
Enter fullscreen mode Exit fullscreen mode
  • You need to make sure deploy_key matches whatever name you gave to the repository secret that you configured above
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
Enter fullscreen mode Exit fullscreen mode
  • Hugo deployments are usually set up with /public as a submodule from another repo (the hosted static site repo). I got tied in knots here with this, so ended up setting external_repository and it works just fine. /public is the folder that the Hugo action pushes by default
          external_repository: rmoff/rmoff.github.io
Enter fullscreen mode Exit fullscreen mode
  • This last setting is a crucial one. I use a custom domain on my GitHub Pages site, and when I was setting this up that custom domain kept getting dropped each time I ran the action…very confusing! Until I realised that the custom domain name is stored as a file CNAME in the repository, and the action was replacing the contents of the repository each time it ran! So, without keep_files the CNAME file in the target repo will get removed and the custom domain configured for GitHub pages will get dropped every time the action runs…
          keep_files: true
Enter fullscreen mode Exit fullscreen mode

The only issue I had was to do with my theme, and some dodgy include directives that I’d set up. I ended up ditching the theme that I use as a submodule and just including it in my site code. Dirty, but it works, and the theme isn’t actively developed any more, so ¯_(ツ)_/¯

Summary

That’s pretty much it. The build chugs away every few minutes, meaning that posts that I’ve written that are future dated will hopefully publish just fine when their time comes around.

actions

Gunnar noted that he’s evolved his build from what he originally published, as well as setting up preview builds for PRs which is a neat idea.

Top comments (0)