GitHub Actions Workflows: an Introduction

A GitHub Actions workflow is a script that lives in a repository and runs when something happens to that repository.

  • Where it lives: a YAML file in the repository's .github/workflows/ directory, committed like any other file.
  • When it runs: on the events it names — a push, a pull request, a button press, a schedule.
  • What it runs: shell commands, and ready-made steps called actions, on a fresh machine GitHub lends it.
  • How it is organised: commands are grouped into jobs; each job runs on a machine of its own.

A first workflow

A workflow that greets every push and every pull request, in two jobs:

.github/workflows/hello.yml:

 1name: hello
 2
 3on:
 4  push:
 5  pull_request:
 6
 7jobs:
 8  greet:
 9    runs-on: ubuntu-latest
10    steps:
11      - name: Say hello
12        run: echo "Hello from $GITHUB_ACTOR, on branch $GITHUB_REF_NAME"
13
14      - name: Look at the machine
15        run: |
16          whoami
17          pwd
18          ls -la
19
20  list-files:
21    runs-on: ubuntu-latest
22    needs: greet
23    steps:
24      - name: Check out the repository
25        uses: actions/checkout@v7
26
27      - name: List its files
28        run: ls -la
  • on: names the events that start it.
  • jobs: holds two jobs, greet and list-files.
  • runs-on: picks the machine each job runs on.
  • steps: are the commands, run one after the other.

Once this file is pushed, the push itself starts the first run.

Events: what starts a workflow

GitHub watches the repository, and starts every workflow whose on: names the event that just happened.

EventHappens when
pushcommits are pushed to a branch, or a tag is pushed
pull_requesta pull request is opened, or new commits are pushed to it
workflow_dispatchsomeone presses Run workflow in the Actions tab
schedulea time comes round, written as a cron line: - cron: '0 6 * * 1' is Mondays at 06:00 UTC
issues, release, …an issue is opened, a release is published, and many more

An event can be narrowed down:

1on:
2  push:
3    branches: [main]      # pushes to main only
4    paths: ['app/**']     # and only when a file under app/ changed

The run knows what started it: which event, which commit, which branch, who pushed. It reads them as environment variables ($GITHUB_SHA, $GITHUB_REF_NAME, $GITHUB_ACTOR) or as expressions in the YAML (${{ github.sha }}).

Jobs: a group of steps on one machine

A job is a list of steps that run in order, on one machine, called a runner.

  • The runner is a fresh virtual machine. GitHub creates it when the job starts and throws it away when the job ends.
    • ubuntu-latest, windows-latest and macos-latest are GitHub's own machines.
    • They come with the usual tools installed: git, docker, Python, Node, the AWS CLI, kubectl, gh, and more.
  • The runner starts empty: the repository is not on it.
    • That is why most jobs begin with actions/checkout, which clones the repository onto the runner.
  • Jobs share nothing. Each has a machine of its own, so a file one job makes is not on the next one's.
  • Jobs run in parallel, unless one names another in needs:; then it waits for that job to succeed.
    • In the first workflow, list-files starts only after greet has passed.
  • A failed step fails its job: a command exiting with a non-zero code stops the job there.

Steps: shell commands and actions

A step is one of two things.

  • run: — shell commands, run by bash on Linux and macOS, PowerShell on Windows.
    • Anything a shell can do on that machine: build, test, call docker, curl an API, run a script from the repository.
    • A | after run: holds several lines, run as one script.
  • uses: — an action: a ready-made step someone packaged, named owner/repository@version.
    • actions/checkout@v7 clones the repository; docker/login-action@v4 logs in to a registry.
    • Its inputs go under with:.
    • Thousands are listed in the GitHub Marketplace.
1      - name: Log in to Docker Hub
2        uses: docker/login-action@v4
3        with:
4          username: alice
5          password: ${{ secrets.DOCKERHUB_TOKEN }}

Secrets and variables

A workflow is committed to the repository, so it must not hold a password. GitHub keeps such values in the repository's settings instead, under Settings → Secrets and variables → Actions.

  • A secret is written once and never shown again; a run reads it as ${{ secrets.NAME }}, and GitHub masks it as *** in the log.
  • A variable is a plain value, such as a user name or a region; a run reads it as ${{ vars.NAME }}.

Runs and their logs

Every time a workflow starts, GitHub records a run, under the repository's Actions tab.

  • A run shows its jobs, each job its steps, and each step its output, line by line.
  • A green tick or a red cross marks each run, and appears beside the commit and on the pull request that started it.
  • A failed run can be started again with Re-run jobs, on the same commit.

For the first workflow, the greet job's log reads like this:

1Say hello
2  Hello from alice, on branch main
3Look at the machine
4  runner
5  /home/runner/work/hello-actions/hello-actions
6  total 8
7  drwxr-xr-x 2 runner docker 4096 Oct 15 09:12 .
8  drwxr-xr-x 3 runner docker 4096 Oct 15 09:12 ..
  • The job runs as the user runner, in a directory named after the repository.
  • The directory is empty: greet has no checkout step. The list-files job, which checks out first, lists the repository's files there.

The Pipeline Identities labs build on this: a workflow whose job reaches Docker Hub, AWS, a Kubernetes cluster and Slack, and the credentials each one asks for.