GitHub Actions

Overview

GitHub Actions enable registering "jobs" to run on a cloud server in response to GitHub events. The cloud server must have the GitHub Actions runner application is installed. GitHub provides these servers for free, but it is also possible to run jobs on your own servers.

For example, a workflow can build an application after every push to given branches. This can include running linters, code formatters, and tests. Executed workflows and their output appear in the "Actions" tab of each GitHub repository.

A shell command can run a shell script or a CLI command such as those provided by npm. Examples include npm install, npm run lint, and npm test.

There are over 3,000 predefined actions to choose from, cataloged at https://github.com/actions and https://github.com/marketplace?type=actions. Many of these are commercial, but there are over 200 that have a free tier.

Configuring Workflows

Workflows for a repository are configured by YAML files in the .github/workflows directory. Any number of workflows can be triggered by a single GitHub event. For details on the syntax of these files, see https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions.

Here is a simple example defined in a file named demo.yml. It uses an action defined at https://github.com/actions/hello-world-javascript-action. The code that implements actions is automatically retrieved from their GitHub repositories.

name: My Demo # workflow name
on: push
jobs:
build: # job id
name: DemoJob # job name
runs-on: ubuntu-latest
steps:
- name: Hello
id: hello # used below to refer to output from this step
uses: actions/hello-world-javascript-action@master
with: # specifies arguments to the action
who-to-greet: 'Mark Volkmann'
- name: Time
run: echo 'The time was ${{ steps.hello.outputs.time }}.'

This executes on every push to the repository on any branch.

The on property can be set to one webhook event name or an array of them. There are many webhook events that can trigger a workflow to run. These are documented at https://help.github.com/en/actions/reference/events-that-trigger-workflows. Some events are triggered by more than one kind of activity. When this is the case, a particular activity type can be specified.

Workflow Jobs

In the workflow defined above, build is a job id. This workflow defines a single job. Defining multiple jobs is useful to allow some steps to run on a server that uses a different operating system.

Workflow Steps

Each step (a.k.a. action) within a job is defined by a number of properties.

Property NameMeaning
nameoptional step name that appears in the web UI that shows workflow results
runa shell command to run
usesa predefined action to use
witharguments to pass to the action
idname that will be used to refer to action result properties
needsstep name (or array of them) that must completed before this step begins
iffor conditional execution

Specify run or uses, but not both.

If a step does not have a name property, a name is created from the value of run or uses.

The id property specifies a property name whose value will be an object with an outputs property that is an object that holds all the output values. Actions typically document their outputs. In the example above, the action creates an output named time which can be accessed with steps.hello.outputs.time because the value of id is hello. The action sets this to the time at which it was executed.

Supported Webhook Events

Webhook EventTriggered By
check_runthe "check runs" API is invoked; can check code in various ways (ex. linting)
check_suitea suite of check runs is executed
createa branch or tag is created
deletea branch or tag is deleted
deploymenta request to deploy a branch, SHA, or tag is received
deployment_statusa deployment status is provided by an HTTP POST request to a GitHub API
forka repository is forked
golluma wiki page is created or updated
issue_commentan issue comment is created, edited, or deleted
issuesan issue is opened, edited, deleted, transferred, pinned, unpinned, closed, reopened, assigned, unassigned, labeled, unlabeled, locked, unlocked, milestoned, or demilestoned
labela label is created, edited, or deleted
milestonea milestone is created, closed, opened, edited, or deleted
page_builda GitHub Pages-enabled branch is pushed
projecta project within a repo is created, updated, closed, reopened, edited, or deleted (see the "Projects" tab in a GitHub repo to manage project tasks)
project_carda project card is created, moved, converted to an issue, edited, or deleted
project_columna project column is created, updated, moved, or deleted
publica private repo is changed to public
pull_requesta pull request is opened, assigned, unassigned, labeled, unlabeled, edited, closed, reopened, synchronize(d), ready_for_review, locked, unlocked, review_requested, or review_request_removed
pull_request_reviewa pull request review is submitted, edited, or dismissed
pull_request_review_commenta pull request review comment is created, edited, or deleted
pusha commit is pushed
registry_packagea registry package (npm alternative) is published or updated
releasea release is created, published, unpublished, edited, prereleased, or deleted
statusthe status of a commit changes
watcha user watches or stars the repository

Scheduled Workflows

A workflow can be scheduled to run at a certain time interval by defining a "scheduled event". For example, adding the following in a workflow YAML file causes the jobs that follow it to run every five minutes:

on:
schedule:
- cron: '*/5 * * * *'

The parts of the cron value, in order, are minutes (0-59), hours (0-23), day of month (1-31), month (1-12), and day of week (0-6). An asterisk is treated as a wildcard character, allowing any value. Specifying a minute value with */n means to run at every nth interval, so */5 runs every five minutes.

Manually Triggered Workflows

A workflow can be triggered to run by dispatching a repository_dispatch event. This is done by sending an HTTP POST request to a GitHub API endpoint.

Define the events that should trigger a workflow in a workflow YAML file as follows:

on:
repository_dispatch:
types: [start-my-workflow]

For details on sending a POST request for the specified event type in order to trigger the workflow, see https://developer.github.com/v3/repos/#create-a-repository-dispatch-event and https://dev.to/teamhive/triggering-github-actions-using-repository-dispatches-39d1. These describe the required request headers (including an Authorization header containing a personal access token) and body.

Operating System

For workflows that will run on a GitHub-hosted server (a.k.a. runner), the operating system of the server can be specified. This can be the latest version of a particular OS or a specific version. Current options include:

Workflows can also be self-hosted. An example of specifying this is:

runs-on: [self-hosted, linux]

Viewing Action Results

After a workflow is triggered, click the "Actions" tab of the GitHub repository to refresh the list of triggered workflows. This can be done repeatedly until the workflow appears.

GitHub Actions web UI #1

The workflows that have been executed can be filtered on the event that triggered them (ex. "push"), their status (ex. "success" or "failure"), the targeted branch, and the GitHub user name that triggered it.

Clicking a workflow row displays details about its execution.

GitHub Actions web UI #1

Click a job name (ex. "DemoJob") in the left nav to see details. This displays information about the steps "Set up job", "Complete job", and each named step in the workflow, "Hello" and "Time" in this example. Click the disclosure triangle in front of a step name to see its detail.

The "Set up job" step sets up the cloud environment where the workflow will execute and downloads all predefined actions that will executed.

The "Complete job" step tears down the cloud environment, stopping any processes that were started.

Here we see the "Set up job" and "Hello" steps expanded.

GitHub Actions web UI #1

The "Set up job" step shows the operating system that was used and actions that were downloaded (ex. "hello-world-javascript-action").

Next, we see the "Time" and "Complete Job" steps expanded. In this example the "Time" step shows the time at which the "Hello" step was executed.

GitHub Actions web UI #2

Workflow Templates

A workflow can be created from the GitHub web UI. Click the "Actions" tab and press the "New workflow" button. This presents a series of boxes that describe workflow templates. Click the "Set up this workflow" button inside one of the boxes to create a workflow based on that template.

Here is an example workflow file created from the "Node.js" template:

name: Node.js CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
build:
runs-on: ubuntu-latest

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

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm test
env:
CI: true

After clicking the button, it offers to save this workflow file in .github/workflows/nodejs.yml within your repository. You can customize the file name and the workflow definition if desired.

When ready to save it, press the "Start Commit" button in the upper-right. A dialog will appear. Optionally enter a commit comment and press the "Commit new file" button. Do a "git pull" to get the new workflow file in your local repository. The new workflow will be scheduled to run immediately. Click the "Actions" tab to see the results.

Two events trigger this workflow to execute. The first is a push to the master branch. The second is creating a pull request on the master branch.

Setting strategy.matrix.node-version to an array of version numbers causes it to execute the steps in each listed version of Node. To only use the latest version starting with 12, remove strategy.matrix.node-version and change the node-version property for the setup-node step to 12.x instead of ${{ matrix.node-version }}.

This workflow runs the following commands:

The following action checks out the repository in the cloud environment.

The following actions setup the environment to use a particular programming language.

The following actions are used by the "Deploy to Amazon ECS" workflow for AWS.

The following action is used by the "Build and Deploy to GKE" workflow for GCP. GKE stands for "Google Kubernetes Engine". GCP stands for "Google Cloud Platform".

Multi-line Steps

In this example, there are multiple, consecutive steps that each run one command.

- run: npm ci
- run: npm run lint
- run: npm run format
- run: npm run build

Alternatively, this can be written as a single step with multiple commands as follows:

- run: |
npm ci
npm run lint
npm run format
npm run build

Workflow Errors

If a workflow step results in an error, subsequent steps will not be executed. For example, this could happen if there is a code linting error, a compiler error, or a test failure.

If there are any errors, the repository owner will receive an email with the subject "[{username}/{repo-name}] Run failed: {workflow-name} - {branch-name}". The email will include two links that can be clicked to see the results. The link containing the failed job name is more informative than the link containing "View results".

Using Secrets

To use secret information like passwords and tokens in a workflow, create GitHub secrets.

To add a secret to a GitHub repo:

Secrets can then be referenced by name in a workflow with the syntax ${{ secrets.name }} where name is the secret name. This is used in the workflow in the next section.

Building and Publishing an Eleventy Site

Here is a workflow file that builds and deploys an Eleventy site on every push.

name: Eleventy build and deploy
on:
push:
branches: [master]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: npm run lint
- run: npm run format
- run: npm run build
- name: site deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GH_TOKEN }}
publish_dir: ./_site

For more details on the "site deploy" step, see https://github.com/peaceiris/actions-gh-pages.

Manually Triggering a Workflow

Currently there is no way to manually trigger a workflow from the GitHub web UI. It is a frequently requested feature, as indicated at feature request.

However, workarounds are possible. One workaround is to configure a workflow to be triggered when the repository is starred. This is done by changing the on property at the top of a workflow file to the following:

on:
push: # existing event
branches: [master]
watch: # newly added event
types: [started] # triggered by starring the repo
# Why is the event name "started"? Looks like a typo.

Output with echo

When running in a Linux environment, a step can use the echo command to produce output. For example:

- name: hello world
run: echo "Hello, World!"

To see the output, browse the GitHub repository, click the "Actions" tab, select the workflow, select the job, and click the disclosure triangle for the step. If the step has no disclosure triangle, click the ellipsis in the upper-right and select "View raw logs".

Shell Scripts

A step can execute a shell script that exists in the repository. For example:

- name: run shell script
run: . ./bin/example.sh

Contexts

A large amount of context information is available in workflows. It is stored in the following context objects: env, github, job, matrix, needs, runner, secrets, steps, and strategy. To learn more about these, see contexts.

To output the data in a particular context in a step, use the toJson function to put the JSON representation of the object in an environment variable and then use an echo shell command to output it. For example:

- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"

Sending Email

A step can send an email. For example, this workflow sends an email when a project board in the repository changes.

name: GitHub Project changes
on: [project, project_card, project_column]
jobs:
project-change:
runs-on: ubuntu-latest
steps:
- name: dump GitHub context to discover available data
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: email summary
id: projectChange
uses: dawidd6/action-send-mail@v2
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{secrets.MAIL_USERNAME}}
password: ${{secrets.MAIL_PASSWORD}}
subject: blog project change
body: >
A change was detected in the
<a href="https://github.com/mvolkmann/blog/projects/3">project board</a>
of the GitHub repository ${{github.repository}}.
<br>
updated by ${{ github.actor }}
at ${{ github.event.project_card.updated_at }}
<br>
event name = ${{ github.event_name }}
<br>
event action = ${{ github.event.action}}
<br>
event changes = ${{ toJson(github.event.changes) }}
<br>
card note = ${{ github.event.project_card.note }}

to: r.mark.volkmann@gmail.com
from: Mark Volkmann
content_type: text/html

This requires adding secrets with the names MAIL_USERNAME and MAIL_PASSWORD. These steps assume that Gmail is being used.

  1. Browse https://myaccount.google.com/.
  2. Click "Security" in the left nav.
  3. In the "Signing in to Google" panel, click "App passwords".
  4. In the "Select app" drop-down at the bottom, select "Mail".
  5. In the "Select device" drop-down, select your current device type.
  6. Press the "GENERATE" button.
  7. Copy the displayed password.
  8. Browse the GitHub repository for the workflow being created.
  9. Click the "Settings" tab.
  10. Click "Secrets" in the left nav.
  11. Click "Add a new secret".
  12. Enter "MAIL_USERNAME" for the name.
  13. Enter your Google username.
  14. Press the "Add secret" button.
  15. Click "Add a new secret".
  16. Enter "MAIL_PASSWORD" for the name.
  17. Paste the generated password for the value.
  18. Press the "Add secret" button.

For more detail in the action-send-mail action, see https://github.com/dawidd6/action-send-mail.

Conditional Steps

A step can include an if property to make its execution conditional. For a list of available variables that can be used in the condition, see https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#contexts.

One variable that can be used holds the name of the event that triggered the workflow. The following step outputs this:

- name: report event
run: echo github.event_name = ${{ github.event_name}}

For example, a step can execute only if the workflow was triggered by a particular event.

name: Demo trigger on watch
on:
push:
branches: [master]
watch:
types: [started]
jobs:
demo:
runs-on: ubuntu-latest
steps:
- name: manual trigger
if: github.event_name == 'watch'
run: echo "I was manually triggered."

To trigger this workflow, star the repo. To trigger it again, unstar and star the repo again.

The reported action name will be the workflow name rather than a commit message since it was not triggered by a commit.

Setting Output

A step can set output that can be used in subsequent steps. It uses some crazy syntax to do this. For example:

- name: set some output
run: echo "::set-output name=foo::bar"
id: my_id
- name: use some output
run: echo ${{steps.my_id.outputs.foo}}

This outputs "bar".

Multiple Workflows

A repository can define any number of workflows by creating multiple YAML files in the .github/workflows directory.

An event can trigger any number of workflows. Each triggered workflow appears in the "Actions" tab of the GitHub web UI for the repository as a separate entry. When triggered by a push, they will all have the same title which is a commit comment. Below each title is the workflow name which is what distinguishes them. Click them one at a time to see their results.

Defining Actions

An action is defined by a YAML file that describes an object with the following properties:

Actions can be implemented in a Docker container using any programming language supported by Docker. They can also be implemented outside of a Docker container using JavaScript.

Actions that are intended to be used by only a single repository can be defined in that repository. Actions that are intended to be shared across projects should be defined in their own GitHub repository.

When a workflow uses an action, it can specify the version to use with semantic versioning (major.minor.patch) or just a major version number.

Let's define a JavaScript action that outputs the day of the week for the current date or a provided date.

Create the directory src/actions/day-week. In this directory, create the file action.yml with the following content to describe the action:

name: 'Day of Week'
description: 'outputs the day of the week for today or a given date'
author: 'mvolkmann'
inputs:
date:
description: 'a date in the format "yyyy-mm-dd" or the string "today"'
default: 'today'
outputs:
dayOfWeek:
description: 'a day of the week'
runs:
using: 'node12'
main: 'index.js'

In the same directory, create the file index.js that implements the action.

const core = require('@actions/core');

// Can access information about the
// GitHub repository that is using this action.
//const github = require('@actions/github');

const days = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
];

try {
const dateString = core.getInput('date');
const date = dateString === 'today' ? new Date() : new Date(dateString);
const dayOfWeek = days[date.getDay()];
console.log(dateString, 'is on a', dayOfWeek);
} catch (error) {
core.setFailed(error.message);
}

To use this action, add the following steps to a workflow:

- name: get day of week for today
uses: ./src/actions/day-of-week
- name: get day of week for my birthday
uses: ./src/actions/day-of-week
with:
date: '1961-4-16'

For more details on defining JavaScript actions, see https://help.github.com/en/actions/building-actions/creating-a-javascript-action.