This website is based on the Hugo static site generator. The content itself is stored in a private GitHub repository. A pretty standard setup, but I always felt it was a bit of a hassle to push content to the live website. That's served on a Linux VM running Caddy. To deploy, I needed to run Hugo on my local machine to generate the website and then I need to execute rsync to deploy them to the webserver.

With scheduled posts, it's a bit more annoying as you need to deploy the site again after the date on which the post is scheduled to be posted. That's something I always tend to forget.

Wouldn't it be easier if I just had to commit the content to the git repository and that all the rest would just happen automagically?

We'll, it's not that hard to automate thanks to GitHub actions. The prerequisites are the only tricky part in the whole setup.

Before we can define the action, we first need to get our SSH private key and the fingerprint of the server we want to send the data to.

To get the SSH private key, you can just execute:

$ cat ~/.ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

To get the fingerprints, we can use the ssh-keyscan function using the hostname we intend to deploy to:

$ ssh-keyscan www.yellowduck.be
# www.yellowduck.be:22 SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8
www.yellowduck.be ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAGyGeW/A/d5uzUzvmLN0wbkA13OzAGfm+Qzsi0UfAWX
# www.yellowduck.be:22 SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8
www.yellowduck.be ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCfGYe98PCJa0eQGkER6KLQOQBlo9Y3D1iVjvW+AwJUmswjjyniJDkynylquoL2vkEu3P0fgMYDoVwCbwRkbRtgmHyBlHQbbgnAkYnG4QUwglfDe0d0k668c9ti3kfgF2M+s0disTdgGykUWcLXs02n4Fsz6id3/HNRCI59roNxMt0VioE3ZayMaRzT6JCYhp7Zf/YiMfWphCeRF49jKs8BoRqZc5EbQAlTePBtw4PS10AYAWLawV42kt7wetxevTVQoUJfljCzUE28at7BRHOgy/19tJ+UaokCPba7mL4pvs6348pbPpPHluynkcD+KNFoM3I/KCyhUq2MBhM+k6Ab
# www.yellowduck.be:22 SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8
www.yellowduck.be ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMkVOX5JpzQmtUIhDPWu/+A0UjczIZ0V4Gzdbet5lB4Ee6mcQrvQ//g+pEsfbAFMIOSw2h75wykNoZ4r5y0SZVo=

Once we have these, we'll store them as secrets in our GitHub repository. To do so, browse to the settings page of your repository on GitHub and select "Secrets". You'll need to add two secrets:

  • SSH_KEY which needs to contain your SSH private key
  • KNOWN_HOSTS which needs to contain the output of the ssh-keyscan command

Now that we have that in place, all which is left is to define the GitHub action itself. First, start with creating a new file in your repository unde .github/workflows/deploy.yaml.

The fill it with the actual action definition:

name: Build and Deploy

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
  schedule:
    - cron:  '0 * * * *'

jobs:
  build:
    name: Build and Deploy
    runs-on: ubuntu-latest
    steps:

    - name: Install SSH key
      uses: shimataro/ssh-key-action@v2
      with:
        key: ${{ secrets.SSH_KEY }}
        name: id_rsa
        known_hosts: ${{ secrets.KNOWN_HOSTS }}

    - name: Checkout code
      uses: actions/checkout@v2

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

    - name: Build
      run: hugo --minify

    - name: Copy to webserver
      run: rsync --delete -rvzh ./public/ user@www.yellowduck.be:/var/www/html/yellowduck.be/

Once you commit that, a number of things will happen. Everytime you commit or merge to the master branch, the action will run. Additionally, it will run once on an hour as well with the contents of the master branch. I did both to ensure that when I commit something, an update is deployed immediately. To tackle the scheduled posts, I run the action hourly.

Let's have a look at what the action does.

The option name just defines the name of the action:

name: Build and Deploy

The on option defines when the action is triggered:

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
  schedule:
    - cron:  '0 * * * *'

When the action runs, it just performs a single job called Build and Deploy. It's using a Ubuntu Linux container to run the job on:

jobs:
  build:
  name: Build and Deploy
  runs-on: ubuntu-latest

The job itself consists of several steps.

The first step is to get the SSH key installed by using the Install SSH Key action:

- name: Install SSH key
  uses: shimataro/ssh-key-action@v2
  with:
  key: ${{ secrets.SSH_KEY }}
  name: id_rsa
  known_hosts: ${{ secrets.KNOWN_HOSTS }}

It's using the secrets we defined earlier to do it's work.

The next step is to checkout the code from the git repository:

- name: Checkout code
  uses: actions/checkout@v2

After the checkout, we use the Hugo action to install Hugo on the container:

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

With Hugo installed, we can then build the minified version of the site:

- name: Build
  run: hugo --minify

Last but not least, we can use rsync to deploy the site:

- name: Copy to webserver
  run: rsync --delete -rvzh ./public/ user@www.yellowduck.be:/var/www/html/yellowduck.be/

After committing this to the repo, you can use the "Actions" tab in your repository to monitor what's happening…

GitHub Action output

If the action happens to fail, you'll be notified by email.