GitHub Actions is a Continuous Integration, Delivery, and Deployment workflow manager made by the code repository hosting service GitHub. GitHub was a little late in the CI/CD pipeline management compared to its competitor GitLab and the good old Jenkins. After a beta testing period, the service opened in 2018.

GitHub Actions is fully integrated with the service, accessible via the “Actions” tab on a repository (if the owner enabled it). The workflows are written in YAML with a declarative language describing the event triggering the pipeline, the runner kind, some environment-related informations, and the jobs definitions. The workflows are declared as YAML files placed in a .github/workflows/ folder at the root of the repository. A workflow can be modified in a branch and ran from it in the Actions tab, you don’t have to stick to your main branch.

An Action workflow is ran by a “runner”. The runner is an execution environment containing several runtimes, build, test and deployment tools. GitHub provides hosted runners that are virtual environments in the Microsoft Azure infrastructure. Three kind of runners are available : Linux-based (Ubuntu), macOS, and Windows Server. Each of them have two or three versions available. The GitHub hosted runners are based on Microsoft Azure Standard_DS2_v2 virtual machines providing 2 vCPU and 7GB RAM. Exception for the macOS runners that are based on GitHub’s own macOS Cloud. The runner can also be self-hosted as the software is released under the MIT license.

As a SaaS provider, GitHub Action has subscriptions plans granting more or less capabilities of Action usages. In this case, the limitation is the runner usage in minutes. At the time this article is written, the subscriptions plan are the following ones :

  • Free : available with all public repositories and free accounts, granting 2 000 minutes / month
  • Pro : 3 000 minutes / month
  • Team : 3 000 minutes / month
  • Enterprise : 50 000 minutes / month

The additional hosted runner minutes plan are :

  • 0.008$ / minute for a Linux
  • 0.016$ / minute for a Windows
  • 0.08$ / minute for a macOS

Self-hosted runners are completely free of usage charges, excepted your hosting costs of course. They can be hosted on a server or, preferably, in a Kubernetes cluster that can scale the number of workers according to the load.

As said above, a GitHub Action workflow is a YAML file containing at least the following parts :

  • An event that triggers the workflow (manual, scheduled, on pull request, on merge…)
  • One or more Jobs : a group of workflow actions
  • A runner to run on
  • A step representing an action to execute in the job

Example of a Node.JS application build workflow :

name: Build my Node.JS application
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubunt-latest
    steps:
      - uses: actions/checkout@v3
      - name: Use node.js 16.x
        uses: actions/setup-node@v3
        with:
          node-version: '16.x'
      - name: Install dependencies
        run: npm ci
      - name: Build
        run : npm run build
      - name: Test
        run: npm run test

Let’s explain this :

  • name : Optional, will display the value instead of the workflow filename in the GitHub Action list
  • on : This is the Trigger event. In this case, we have two triggers : on push on the main branch, and on pull request on the main branch.
  • jobs : The job list of the workflow
    • build : the “build” job, it will be displayed with this name on the Action viewer
      • runs-on : the runner that will be used. In this case, the latest Ubuntu available
      • steps : each step of the job, that’s the smallest action unit available
        • name : a display name for the step, it’s optional
        • uses : this keyword tells to use a marketplace Action (we’ll see that after). In this case : actions/setup-node with version 3.x
        • run : another action keyword. This one executes shell commands

Each jobs and steps have input and output contexts, which means a step can use the output of a previous one as an input for itself. For example, a first step uses the REST API Action to query some information, the second parse and process the result. Each step can also use conditional or parsing expressions. Basically, at least on Linux runners as I haven’t tested the others, each step is transformed into a sub-shell script executed at once.

Now, let’s talk about the marketplace mentioned above. The Actions marketplace is a repository on which everyone can create an Action and make it available for GitHub’s users. GitHub itself maintains some official Actions, but they also have a “verified creator” certification indicating the creator has been verified. For example, Actions made by Red Hat, Cloudflare, or Atlassian are verified that they’re indeed the expected companies. But GitHub won’t certify the Action itself. You can make you own Actions, in a container or in JavaScript.

The Actions Marketplace remind me in some ways the same problem as Jenkins. It relies a lot on its plugins library which is enormous, but it’s sometimes a gamble. You will find something interesting, but the Action is not maintained anymore or poorly documented, just like Jenkins’ plugins main problem in my experience. And sometimes, even official GitHub Actions are not anymore maintained, routing the users to a community provided Action. I’m not a big fan of this way, mainly because of this idea :

xkcd 2347 xkcd Dependency - License CC-BY-SA-NC 2.0

I’m using GitHub Action since almost one month now, as one of my clients choose it for its CI/CD usage. In my career, I’ve mainly used Jenkins and I think that GitHub Actions marketplace has the same issue as Jenkins’ plugins library. Sometimes you’ll find something that is exactly what you need, but it’s not maintained anymore (and possibly broken or breached). Sometimes, the documentation will be inexistant or reduced to the most minimalist way forcing you to try things and read the code to understand how does it works. Another moment, you’ll find a plugin that covers half of your needs and you’ll have to compensate. And sometimes everything’s good until it’s broken and you pass a day to understand why. With Jenkins, I’ve learned to avoid using too much third-party utilities because of the randomness of their evolution. For example, most Microsoft Azure plugins are now deprecated and recommend to directly use the az command-line tool. Fortunately, I’ve did it since the beginning.

Despite these apprehensions, GitHub Action is working well and the various triggers are nicely integrated with the rest of the ecosystem. The marketplace is pretty-well furnished and the possibility to make your own Actions is not that complicated (maybe I’ll write something about this if I have to opportunity to do some).

However, I’ve encountered some weird behavior in the shell tasks where the commands are not processed in the same way as on my own laptop (running on Fedora, so why an Ubuntu-based runner would have difficulties to run basic commands ?), maybe because of the pre-processing of the step by the runtine, and that’s very annoying. Another annoying thing is as the workflow is stored on GitHub, you need to commit / push every modification to test them. When you are learned to use them and, like me, you work in a yaks and away model, that could be very boring. Fortunately, I’ve found this project that allows to execute locally a workflow based on a container image, very helpful ! I’ll write about this on a next article.