Test & build a project with GitHub Actions
GitHub Actions is an automated workflow system that GOV.UK uses for Continuous Integration (CI). In GOV.UK RFC 123 we decided that GitHub Actions is the preferred platform for GOV.UK CI usage where the wider platform integration of Jenkins is not required.
If your workflow requires the use of secrets, please talk to GOV.UK senior tech before deploying it. This is to help GOV.UK establish consistent and effective secret management for GitHub Actions across the wider alphagov GitHub organisation.
GOV.UK Conventions for GitHub Actions
Name of CI workflow file
You should name the file used for configuring the CI workflow ci.yml
.
It should live in the .github/workflows
directory.
Name of CI workflow and jobs
The CI workflow configured above should have a name
property with a
value of CI
. The workflow should have a number of self-contained jobs
configured, for actions such as running tests, linting and security scans.
These should use reusable workflows where
possible. For example:
jobs:
security-analysis:
uses: alphagov/govuk-infrastructure/.github/workflows/brakeman.yml@main
lint-javascript:
uses: alphagov/govuk-infrastructure/.github/workflows/standardx.yml@main
with:
files: "'app/assets/javascripts/**/*.js'"
lint-ruby:
uses: alphagov/govuk-infrastructure/.github/workflows/rubocop.yml@main
The jobs and steps do not need to be given a name attribute
as the job/step key should be sufficiently descriptive. In contrast, the
ci.yml
must have a name
property called CI
, as this is
relied upon by our automated tooling.
We previously recommended that repos should always define a job called test
.
Historically, this job would be responsible for running the repo’s tests, linter
and so on, usually via something like bundle exec rake
. In January 2023, a
new convention
was established, giving each task its own ‘job’ (e.g. ‘test-ruby’ for running
tests and ‘lint-ruby’ for running the linter), within an overall ‘workflow’
called CI
. Whilst this decoupling does make it harder to run the entire check suite
locally, it allows us to take advantage of GitHub Action parallelisation, as well
as better code reuse through reusable workflows.
When the CI workflow should run
The workflow should be configured to run when there is a push
to the default branch (typically “main”) and when a
pull_request
opened. This means CI runs when a branch
is merged (the push
to main) and it runs against any changes introduced
in a pull request. We prefer to run against a pull_request
event to all
push
events, as this allows external contributors to have pull requests
tested and because pull_request
runs against a version of the codebase
that is merged with the repository default branch.
Example configuration:
# ./github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request:
We previously recommended CI jobs to run on [push, pull_request]
as per
RFC-123 however this caused duplicate runs of the CI jobs
of a pull request (one for the push
another for the pull_request
)
which were both superfluous for our testing, a point of confusion and were
depleting our allowance quotas.
Running CI on demand
To run CI on-demand, outside a pull request, for example before opening a PR,
you may configure a repository to have a workflow_dispatch
event so you can
run it from the GitHub Actions user interface. If you do this you will need
to configure the checkout action to reference the appropriate commit.
Example configuration:
name: CI
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
inputs:
ref:
description: 'The branch, tag or SHA to checkout'
default: main
type: string
jobs:
# for example
test-ruby:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ inputs.ref || github.ref }}
Branch protection rules
Pull Requests must not be merged until the jobs defined in the CI
workflow have passed. You should always define a workflow called CI
.
Reuse workflows where possible
We define a number of reusable workflows in the govuk-infrastructure repo.
For example, here’s how Content Publisher uses a reusable workflow that is defined in govuk-infrastructure:
name: CI
jobs:
lint-ruby:
name: Lint Ruby
uses: alphagov/govuk-infrastructure/.github/workflows/rubocop.yml@main
CI workflow for Ruby gems
This differs to a simple Ruby application because Ruby gems are not tied to particular Ruby or Rails versions. Therefore we should test against various combinations of supported versions. See example in govspeak.
To do this, we use a build matrix. In this example, we test against three
Ruby versions (2.7
, 3.0
, 3.1
) and two Rails versions (6
, 7
). Combined,
this results in 6 variations for the workflow to test against.
# .github/workflows/ci.yml
on: [push, pull_request]
jobs:
# Run the test suite against multiple Ruby and Rails versions
test_matrix:
strategy:
fail-fast: false
matrix:
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
ruby: [2.7, '3.0', 3.1]
# Test against multiple Rails versions
gemfile: [rails_6, rails_7]
runs-on: ubuntu-latest
env:
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- run: bundle exec rake
# Branch protection rules cannot directly depend on status checks from matrix jobs.
# So instead we define `test` as a dummy job which only runs after the preceding `test_matrix` checks have passed.
# Solution inspired by: https://github.community/t/status-check-for-a-matrix-jobs/127354/3
test:
needs: test_matrix
runs-on: ubuntu-latest
steps:
- run: echo "All matrix tests have passed 🚀"
# We have a shared workflow that can be used for most gem publishing needs. You may have to write
# your own if you have a gem that is released in a complex way.
publish:
needs: test
if: ${{ github.ref == 'refs/heads/main' }}
permissions:
contents: write
uses: alphagov/govuk-infrastructure/.github/workflows/publish-rubygem.yaml@main
secrets:
GEM_HOST_API_KEY: ${{ secrets.ALPHAGOV_RUBYGEMS_API_KEY }}
For each Rails version, a *.gemfile
should exist in a top-level directory called gemfiles
.
# gemfiles/rails_7.gemfile
source "https://rubygems.org"
gem "rails", "~> 7"
gemspec path: "../"
Notes:
- Our preference is to test the gems we publish against the latest version of all currently supported minor versions of Ruby MRI.
- For a real world example of this workflow, see govuk_admin_template.
- The
ALPHAGOV_RUBYGEMS_API_KEY
secret is an organisation secret that is added to individual repositories by a GitHub Admin. Please talk to GOV.UK Senior Tech for this to be added to a repo.