workflow-builder

GH Actions Workflow Builder

The quick and easy way to design a GitHub Actions workflow

MichaelCurrin - workflow-builder Made for GH Actions

Table of contents

About

Contributing

See Contributing guide.

Tips

If you want to know available options for a workflow and if you YAML syntax is valid, edit your workflow on GitHub rather than in a local file. Click on a field or at an indent level where you want to add a field, the press CTRL+SPACE. You’ll see available options for that context. This can save reading through the docs.

Resources

YAML syntax

Name

Example names for your workflow:

name: CI
name: CI Build
name: CI Test
name: Node CI
name: Deno CI
name: Python CI
name: Deno CI
name: Deploy GH Pages
name: Release

Triggers

For more options such as building on schedule, see my Triggers cheatsheet.

You can combine the sections below - for example, you can run a task on pushes main, on a nightly schedule and also whenever you press the run button (without having to make a commit).

On a commit push

Push a local commit or commit using the GitHub UI to trigger these on conditions.

The workflow above has been set up to not run if there are just changes in your docs directory. This is useful to reduce a run that gives no benefit and would still take up processing minutes allocate to your account. If you actually have content in your docs directory that matters like for a documentation site, then of course you can remove the ignore parts.

On a tag or release

Create a tag or a release to trigger your workflow.

On manual button press

Allows you to run this workflow manually from the Actions tab.

on:
  workflow_dispatch:

On a schedule

Run on a given frequency, using Crontab notation.

Daily at midnight:

on:
  schedule:
    - cron:  "0 0 * * *"

This is useful for building a site, deploying an application or publishing to a package registry. Also, if you have any code quality or security scans such as a CodeQL workflow.

Job setup

Name

Give a job a name using a key under jobs and a pretty name under name field.

The job name might be similar to the workflow file’s name at the top, but I find it better to be more specific. Like “Node CI” for the workflow and “Build and deploy” as a job name.

Some samples:

jobs:
  build:
    name: Build
jobs:
  build-test:
    name: Build and test
jobs:
  build-deploy:
    name: Build and deploy

Operating systems

Define the operating system for a job.

Run on Ubuntu. This is the most common flow.

jobs:
  build:
    name: Build

    runs-on: ubuntu-latest

Run on a matrix of operating systems.

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macOS-latest, windows-latest]
      runs-on: ${{ matrix.os }}

This seems to be case-insensitive as some people use macos-latest.

Note that quotes are not needed in YAML, whether for a string or array of strings.

Environment variables

env:
  VAR_A: Hello

jobs:
  build:
    env:
      VAR_B: World

Steps

Define steps for a job to run

This section covers some common snippets across languages, for some of my common flows that build, test and deploy.

A note on wording - “setup” is a noun, while “set up” is a verb. See both under the setup definition on the Merriam-Webster online dictionary.

Cookbook

For more details, please explore the Workflows section in my Code Cookbook.

That covers:

Outline

This is an outline of a generic workflow.

It includes emojis to brighten up the log and make it easier to scan visually. For set up, you might use something related to the language, like a snake for Python.

steps:
  - name: Checkout 🛎️
    uses: actions/checkout@v2

  - name: Set up Foo ⚙️  # e.g. Python 🐍, Node or Ruby 💎
    uses: actions/setup-foo@v2
    with:
      foo-version: '1.x'

  - name: Cache dependencies 💾
    uses: actions/cache@v2
    with:
      # ...

  - name: Install dependencies 🔧
    run: # ...

  - name: Check formatting 🎨
    run: # ...

  - name: Lint 🧐
    run: # ...

  - name: Test 🚨
    run: # ...

  - name: Build 🏗️
    run: # ...

  - name: Deploy to GitHub Pages 🚀
    uses: # ...

Checkout

Using the checkout action.

steps:
  - name: Checkout 🛎️
    uses: actions/checkout@v2

Set up environment

Node

Node CI samples.

GH Actions comes with Node and Yarn set up already. But you can use an action if you ned more recent version, or to use a matrix of Node or Yarn versions.

Using the setup-node action.

steps:
  - name: Set up Node.js ⚙️
    uses: actions/setup-node@v2
    with:
      node-version: '14.x'

Use a matrix of Node versions.

steps:
  strategy:
    matrix:
      node-version: [10.x, 12.x, 14.x]

  steps:
    - name: Checkout 🛎️
      uses: actions/checkout@v2

    - name: Use Node.js ${{ matrix.node-version }} ⚙️
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}

Deno

Deno CI samples.

Using the setup-deno action.

steps:
  - name: Set up Deno ⚙️ 🦕
    uses: denolib/setup-deno@v2
    with:
    deno-version: v1.x

Go

Go CI samples.

Using the setup-go action.

steps:
  - name: Set up Go ⚙️ ⏩
    uses: actions/setup-go@v2
    with:
      go-version: 1.15

Rust

Using an action from the rust-lang/simpleinfra repo.

steps:
  - name: Set up Rust ⚙️ 🦀
    uses: rust-lang/simpleinfra/github-actions/simple-ci@master
    with:
      check_fmt: true

Python

Python CI samples.

Using the setup-python action.

steps:
  - name: Set up Python ⚙️ 🐍
    uses: actions/setup-python@v2
    with:
      python-version: 3.x

Ruby

Using the setup-ruby action.

steps:
  - name: Set up Ruby ⚙️ 💎
    uses: actions/setup-ruby@v1
    with:
      ruby-version: 2.7

Install dependencies

This section is not needed for Go or Deno where packages are installed on running, building, testing, etc.

See GH Actions Cache guide.

The cache step is optional but makes the build faster. It will load dependencies from the cache, if the dependencies file is unchanged. Also adds a clean-up step for you to save dependencies to the cache.

Python

steps:
  - name: Cache Python packages 💾
    uses: actions/cache@v2
    with:
      path: ~/.cache/pip
      key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
      restore-keys: |
        ${{ runner.os }}-pip-
        ${{ runner.os }}-

  - name: Install dependencies 🔧
    run: |
      python -m pip install --upgrade pip
      pip install -r requirements.txt

Node

See related workflows here.

For NPM projects:

steps:
  - name: Cache NPM packages 💾
    uses: actions/cache@v2
    with:
      path: ~/.npm
      key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        ${{ runner.OS }}-node-
        ${{ runner.OS }}-

  - name: Install dependencies 🔧
    run: npm install

For Yarn projects:

steps:
  - name: Get Yarn cache dir 💾
    id: yarn-cache
    run: echo "::set-output name=dir::$(yarn cache dir)"

  - uses: actions/cache@v1 💾
    with:
      path: ${{ steps.yarn-cache.outputs.dir }}
      key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
      restore-keys: |
        ${{ runner.os }}-yarn-

  - name: Install dependencies 🔧
    run: yarn install

This uses Yarn’s cache directory. On Ubuntu this is ~/.cache/yarn/v6.

Ruby

This will setup Ruby in the environment, install gems with Bundler and even cache the gems for faster builds.

steps:
  - name: Set up Ruby 💎
    uses: actions/setup-ruby@v1
    with:
      ruby-version: '2.7'
      bundler-cache: true

This Ruby setup flow works great for Jekyll projects too. Just make sure to add jekyll gem to your Gemfile and add a build step as below.

steps:
  - name: Build 🏗
    run: bundle exec jekyll build --trace

Then your workflow will set up Ruby and Jekyll then build _site directory.

If you want to persist the _site directory output to serve as a GH Pages site, see the GitHub Pages section.

See more Jekyll samples and info in my Jekyll CI recipes.

Build

Create your production build output

Bundle your package or app so it can be installed, or build your website assets.

Make

This depends on setting up Makefile with a build target.

steps:
  - name: Build 🏗️
    run: make build

Node

steps:
  - name: Build 🏗️
    run: npm run build
steps:
  - name: Build 🏗️
    run: yarn build

Jekyll

After setting up Ruby and installing dependencies with Bundler.

Build the site with Jekyll.

steps:
  - name: Build 🏗️
    run: bundle exec jekyll build --trace

If you want to persist the _site directory output to serve as a GH Pages site, see the GitHub Pages section.

Deploy

GitHub Pages

This action will take a given build output directory (like dist, build or _site) and commit it as a single commit on the gh-pages branch at the root path. This can then be served as static assets (HTML, CSS and JS) on a GH Pages site.

For more info and related workflows and actions, see GH Pages in my Code Cookbook.

- name: Deploy to GitHub Pages 🚀
  if: ${{ github.event_name != 'pull_request' }}
  uses: peaceiris/actions-gh-pages@v3
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: _site

Steps:

  1. Run your build command. This could be anything - such using Jekyll, MkdDocs, or npm run build (for React, Vue or Next.js).
  2. Then set up this action to point to that directory e.g. in _site or build.
  3. The action will copy the content root of the gh-pages branch (this it is default behavior).
  4. When that commit is pushed, then your GH Pages site will reload, using the latest content.

Note the use of the if condition on this step. This means that your entire workflow can run on a push to your main branch. But on a push to a Pull Request branch, the earlier build steps will run but the deploy step at the end will be skipped. This is useful to avoid deploy your work in progress branch to your production site.

Workflows out in the world

Here are some workflows I have set up for my projects which I’d like to share.