Easy code is easy to compile and run. That has and always will be true. However, once the code you write spans across multiple classes, files, or even packages it can be hard to properly test, compile, and release this software. Continuous integration (CI) tries to solve this problem. By defining a pipeline of actions to take your code from source to product that run the same way every time. Docker (and containerization in general) augments this process by providing easy to use clean-slate images that these pipelines can built up on. Always starting at the same point and then running the same actions provides a stable pipeline that can reliably test, compile, and release a piece of software. This, in turn, provides a reliable platform that allows developers to more easily solve more problems.

GitHub Actions

There are umpteen solutions for continuous integration. This includes Jenkins, Tekton, and GitHub Actions. Over the past couple of weeks I have been using GitHub Actions (first for work and most recently for this website) and I have found that it drastically increases my productivity by reducing the time I spend on either producing a wheel, running pytest, or generating and uploading this Hugo-driven site.

Pipeline

GitHub uses YAML files to define a CI pipeline. A pipeline is a set of actions in an explicit order. These actions can either be a command to the operating system (e.g. echo "Hello World" or curl www.google.com) or a group of commands developed by someone (e.g. checking out a repo or generating a hugo site). All these actions run on a fresh instance of a virtual machine. There are two choices when it comes to these virtual machines. You can either host your own virtual machine, or use one that GitHub hosts (GitHub hosted platforms are Windows Server 2019, Ubuntu 18.04 and 16.04, and MacOS Catalina).

Below is the pipeline for this site. The pipeline's environment is Ubuntu 18.04 and there are four actions this pipeline takes. The first action (actions/checkout@v2) checks the site repo out. Because the definition for this workflow is in the devel branch that is the branch that this action checks out. The next action (peaceiris/actions-hugo@v2) installs Hugo v0.68.3. The site is then built using a simple action that executes hugo --minify. This builds the site into the public directory. The last action (peaceiris/actions-gh-pages) publishes the public directory into the master branch and adds the CNAME file that allows me to use my own domain name.

# .github/actions/main.yml
name: Build Hugo

on:
  push:
    branches:
      - devel

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

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

      - name: Build
        run: hugo --minify

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public
          publish_branch: master
          cname: www.alexday.me

This is all that GitHub needs to reliably compile and serve my website. Prior to this I had to compile the website locally into a different directory and then commit and push that to the master branch of the repository. Now I can just compile and push to the development branch (something I was already doing) and the changes are immediately reflected by my website.

Secrets

Some pipelines need sensitive variables to function correctly. Rather than commit these variables to the codebase you can enter them as a secret in the settings section of the repository. An action can access these secrets by using the following syntax: ${{ secrets.SECRET_NAME }}. The Hugo deployment pipeline uses the GITHUB_TOKEN secret that comes by default with every repository so that it can push the changes back to the master branch.

Conclusions

GitHub Actions are an amazing addition to the platform. They allow small projects to enable a free and easy continuous integration pipeline while also being able to scale to larger projects. While it is not the end-all-be-all for CI, it is certainly a good jumping off point if you want to start investigating this world. There are some features that were not discussed in this post (namely artifacts as I haven't played with them much), but more information is available in the documentation.