In a cost-saving exercise I wanted to try host my personal website on a free platform, since the code repository is hosted on GitHub, I decided to look into GitHub pages.

My existing workflow combines three repositories to build the website:

  • Hugs (Repo) - Contains my Hugo configuration and assets
  • Blog (Repo) - Contains the blog posts
  • Hugo-theme (Repo) - Contains the Hugo theme

In order to use GitHub Pages I needed a fourth repository named after my GitHub account: (Repo). This is the repository that will contain final static site’s assets, such as HTML, CSS, etc.. and be hosted on

GitHub Actions

GitHub Actions are defined in YAML which defines the steps to run against your repo. Github hosts a library of official & community Actions with configurable behaviour, these are useful for common operations such as checkout a git branch, etc.

I used actions/checkout, peaceiris/actions-hugo & peaceiris/actions-gh-pages.

name: github pages
      - main

Here we define what behaviour the Action triggers for, I specified Push, which means the Action is triggered for every git push origin main that hits my repository. Larger projects will probably want to set this to pull_request or one of the many other events.

    runs-on: ubuntu-latest
      - name: Prepare repos 🥣
        uses: actions/checkout@v2
          submodules: recursive  # Fetch Hugo themes (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

My build process is fairly simple so it’s all grouped in one job called “deploy”. All Actions run in a container, which means we have a choice of Operating System. I don’t have strict requirements so I set this to ubuntu-latest.

steps is where the build process is defined; the first step is to checkout the repo this Action is attached to. In my case this is the hugs repo, and I need to clone each submodule fully.

- name: Checkout to ./public/
  uses: actions/checkout@v2
    repository: joeheaton/
    ref: gh-pages
    path: ./public
    fetch-depth: 0

I clone another repo, this time it’s the that will host the end result, I clone this into ./public, this means when hugo builds my website it will overwrite the contents of, which I can then commit and push.

- name: Setup Hugo 🧪
  uses: peaceiris/actions-hugo@v2
    hugo-version: '0.81.0'
    # extended: true

This is all that’s needed to install any version of hugo thanks to the peaceiris/actions-hugo Action.

- name: Prepare environment 🧪
  run: |
    sudo apt-get install -y sassc

- name: Build 🔨
  run: | make css hugo-prod

Since I defined runs-on: ubuntu-latest at the top of my YAML, I can fulfil one of my dependcies using Ubuntu’s package manager, after that I use my Makefile from hugs to invoke sassc & hugo to build the static site! These commands get executed in the container using the run attribute, we can even use sudo to run as root if needed.

- name: Deploy 📦
  uses: peaceiris/actions-gh-pages@v3
    deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
    external_repository: joeheaton/
    publish_dir: ./public

Lastly we use peaceiris/actions-gh-pages to deploy the modified, cloned to the ./public directory. The Action uses a predefined GitHub Secret called ACTIONS_DEPLOY_KEY to get write access my repository and push the contents of ./public!

Done, now every commit to hugs results in this Action’s YAML being run through from start to finish.

GitHub Secrets

I mentioned the ACTIONS_DEPLOY_KEY which allows the Action to push changes to an external repository, this is how you set that up. I followed this documentation

This will create a new keypair in the current directory, we will add the private key as Secret in the repo with the static site generator, this is done via the web interface and the Secret should be named ACTIONS_DEPLOY_KEY for peaceiris/actions-gh-pages to pick it up by default.

Repository Secrets

Add the public key as a Deploy Key on

Deploy Keys

Once this is done the Actions on hugs can now push changes to!

Domains & CNAME

To complete replacing my old static site with GitHub Pages I need to map my personal domain to the website hosted on

On GitHub navigate to your repository, go Settings and scroll down to GitHub Pages and set your Custom Domain.

Go to your Domain name’s DNS provider and add a CNAME record pointing to your address. Done, you should see your auto-generated GitHub Pages website when you visit your domain!

I haven’t covered enabling HTTPS, you may have noticed there is an option in GitHub to enforce HTTPS for your GitHub Pages custom domain which isn’t checked by default. I didn’t need to go through with this as my website is routed via Cloudflare which enforces HTTPS on my domain. Technically, that might mean Cloudflare is connecting to GitHub insecurely, not sure.


I realised after setting my blog and hugs repos back to private, that the hugs repo is able to clone itself with the checkout@v2 action because it inherently is allowed access to itself, despite being private, but it cannot access blog.

June 2021 – I’ve since migrated to Hugo’s built-in Sass compiler, instead of installing sassc as part of the build process. I just deleted the sassc stage, and added extended: true to uses: peaceiris/actions-hugo@v2 to .github/workflows/hugo.yml.