Peter Stace :: posts :: using-github-actions-for-hugo-cicd

Using GitHub Actions for Hugo CICD

2020-07-14

Background

  • This website is written using Hugo, a static site generator similar to Jekyll and others.

  • I keep the source for the website in a private repo. The source consists of markdown files and HTML templates.

  • I serve the website from a public GitHub Pages repo.

  • When Hugo builds the site, it populates a /public directory in the private repo with the final assets that are ready to be served to site visitors.

  • Up until now, I’ve performed a manual step to bridge the gap between the two. This involves copying the assets from the private repo to the public repo, committing, then pushing the change live.

  • This blog post explains how I automated this process using GitHub Actions.

Automation

  • First I created a GitHub Personal Access Token. This is needed later for pushing to the public GitHub Pages repo. GitHub provides some decent documentation for how to do this. It’s all done through the GitHub website. Since it’s a one off step, there isn’t much point automating it.

  • Then I added the newly created Personal Access Token as a secret for the private website source repo. GitHub also provides some documentation for how to do this. Adding the secret to the repo will allow us to use use the secret in its GitHub Actions workflows. Again, this is done as a manual step via the GitHub website.

  • Finally, we can add the GitHub Actions workflow. The workflow is defined as a YAML file, stored in .github/worflows/deploy.yaml. It has a few interesting aspects.

      name: build
      on:
        push:
          branches:
            - master
      jobs:
        deploy:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v2
            - name: deploy
              shell: bash
              env: 
                GH_TOKEN: ${{ secrets.GH_TOKEN }}
              run: |
                set -exo pipefail
                git config --global user.email "ci@ci"
                git config --global user.name "CI"
                commit_msg="$(git log --pretty='%h %s' -n1)"
                git clone https://$GH_TOKEN@github.com/peterstace/peterstace.github.io.git
                cd peterstace.github.io
                rm -r *
                echo petsta.net > CNAME
                cp -r ../site/public/* .
                git add -A
                git status
                git commit -m "$commit_msg"
                git push
    
  • It only executes when changes are pushed to the master branch. This is controlled by:

      on:
        push:
          branches:
            - master
    
  • When it runs, it first checks out the source repo. This uses a predefined step (provided by GitHub):

      - uses: actions/checkout@v2
    
  • Then it runs a custom bash script step. This step needs to use the secret, which is imported as an environment variable named GH_TOKEN from a secret also named GH_TOKEN:

      env: 
        GH_TOKEN: ${{ secrets.GH_TOKEN }}
    
  • The actual bash script clones the public GitHub Pages repo (peterstace.github.io), deletes its content, then adds the /public directory from the source repo. It makes the assumption that Hugo has already been run on the markdown and HTML templates and the result committed to the private repo. This is a reasonable workflow, since Hugo is run as part of the regular development cycle anyway (as part of creating a new blog post or tweaking a template).

      set -exo pipefail
      git config --global user.email "ci@ci"
      git config --global user.name "CI"
      commit_msg="$(git log --pretty='%h %s' -n1)"
      git clone https://$GH_TOKEN@github.com/peterstace/peterstace.github.io.git
      cd peterstace.github.io
      rm -r *
      echo petsta.net > CNAME
      cp -r ../site/public/* .
      git add -A
      git status
      git commit -m "$commit_msg"
      git push