Note: GitHub-hosted runners are not currently supported on GitHub Enterprise Server. You can see more information about planned future support on the GitHub public roadmap.
Example overview
This article uses an example workflow to demonstrate some of the main CI features of GitHub Actions. When this workflow is triggered, it tests your code using a matrix of test combinations with npm test
.
The following diagram shows a high level view of the workflow's steps and how they run within the job:
Features used in this example
The example workflow demonstrates the following capabilities of GitHub Actions.
Feature | Implementation |
---|---|
Manually running a workflow from the UI | workflow_dispatch |
Triggering a workflow to run automatically | pull_request |
Running a workflow at regular intervals | schedule |
Setting permissions for the token | permissions |
Controlling how many workflow runs or jobs can run at the same time | concurrency |
Running the job on different runners, depending on the repository | runs-on |
Preventing a job from running unless specific conditions are met | if |
Using a matrix to create different test configurations | matrix |
Cloning your repository to the runner | actions/checkout |
Installing node on the runner | actions/setup-node |
Caching dependencies | actions/cache |
Running tests on the runner | npm test |
Example workflow
The following workflow was created by the GitHub Docs Engineering team. To review the latest version of this file in the github/docs
repository, see test.yml
.
Note: Each line of this workflow is explained in the next section at "Understanding the example."
name: Node.js Tests # **What it does**: Runs our tests. # **Why we have it**: We want our tests to pass before merging code. # **Who does it impact**: Docs engineering, open-source engineering contributors. on: workflow_dispatch: pull_request: push: branches: - main permissions: contents: read # Needed for the 'trilom/file-changes-action' action pull-requests: read # This allows a subsequently queued workflow run to interrupt previous runs concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' cancel-in-progress: true jobs: test: # Run on self-hosted if the private repo or ubuntu-latest if the public repo # See pull # 17442 in the private repo for context runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }} timeout-minutes: 60 strategy: fail-fast: false matrix: # The same array lives in test-windows.yml, so make any updates there too. test-group: [ content, graphql, meta, rendering, routing, unit, linting, translations, ] steps: # Each of these ifs needs to be repeated at each step to make sure the required check still runs # Even if if doesn't do anything - name: Check out repo uses: actions/checkout@v3 with: # Not all test suites need the LFS files. So instead, we opt to # NOT clone them initially and instead, include them manually # only for the test groups that we know need the files. lfs: ${{ matrix.test-group == 'content' }} # Enables cloning the Early Access repo later with the relevant personal access token persist-credentials: 'false' - name: Figure out which docs-early-access branch to checkout, if internal repo if: ${{ github.repository == 'github/docs-internal' }} id: check-early-access uses: actions/github-script@v6 env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: github-token: ${{ secrets.DOCUBOT_REPO_PAT }} result-encoding: string script: | // If being run from a PR, this becomes 'my-cool-branch'. // If run on main, with the `workflow_dispatch` action for // example, the value becomes 'main'. const { BRANCH_NAME } = process.env try { const response = await github.repos.getBranch({ owner: 'github', repo: 'docs-early-access', BRANCH_NAME, }) console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`) return BRANCH_NAME } catch (err) { if (err.status === 404) { console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`) return 'main' } throw err } - name: Check out docs-early-access too, if internal repo if: ${{ github.repository == 'github/docs-internal' }} uses: actions/checkout@v3 with: repository: github/docs-early-access token: ${{ secrets.DOCUBOT_REPO_PAT }} path: docs-early-access ref: ${{ steps.check-early-access.outputs.result }} - name: Merge docs-early-access repo's folders if: ${{ github.repository == 'github/docs-internal' }} run: | mv docs-early-access/assets assets/images/early-access mv docs-early-access/content content/early-access mv docs-early-access/data data/early-access rm -r docs-early-access # This is necessary when LFS files where cloned but does nothing # if actions/checkout was run with `lfs:false`. - name: Checkout LFS objects run: git lfs checkout - name: Gather files changed uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b id: get_diff_files with: # So that `steps.get_diff_files.outputs.files` becomes # a string like `foo.js path/bar.md` output: ' ' - name: Insight into changed files run: | # Must to do this because the list of files can be HUGE. Especially # in a repo-sync when there are lots of translation files involved. echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt - name: Setup node uses: actions/setup-node@v3 with: node-version: 16.14.x cache: npm - name: Install dependencies run: npm ci - name: Cache nextjs build uses: actions/cache@v3 with: path: .next/cache key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }} - name: Run build script run: npm run build - name: Run tests env: DIFF_FILE: get_diff_files.txt CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json run: npm test -- tests/${{ matrix.test-group }}/
name: Node.js Tests
# **What it does**: Runs our tests.
# **Why we have it**: We want our tests to pass before merging code.
# **Who does it impact**: Docs engineering, open-source engineering contributors.
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
permissions:
contents: read
# Needed for the 'trilom/file-changes-action' action
pull-requests: read
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
test:
# Run on self-hosted if the private repo or ubuntu-latest if the public repo
# See pull # 17442 in the private repo for context
runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
# The same array lives in test-windows.yml, so make any updates there too.
test-group:
[
content,
graphql,
meta,
rendering,
routing,
unit,
linting,
translations,
]
steps:
# Each of these ifs needs to be repeated at each step to make sure the required check still runs
# Even if if doesn't do anything
- name: Check out repo
uses: actions/checkout@v3
with:
# Not all test suites need the LFS files. So instead, we opt to
# NOT clone them initially and instead, include them manually
# only for the test groups that we know need the files.
lfs: ${{ matrix.test-group == 'content' }}
# Enables cloning the Early Access repo later with the relevant personal access token
persist-credentials: 'false'
- name: Figure out which docs-early-access branch to checkout, if internal repo
if: ${{ github.repository == 'github/docs-internal' }}
id: check-early-access
uses: actions/github-script@v6
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
github-token: ${{ secrets.DOCUBOT_REPO_PAT }}
result-encoding: string
script: |
// If being run from a PR, this becomes 'my-cool-branch'.
// If run on main, with the `workflow_dispatch` action for
// example, the value becomes 'main'.
const { BRANCH_NAME } = process.env
try {
const response = await github.repos.getBranch({
owner: 'github',
repo: 'docs-early-access',
BRANCH_NAME,
})
console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`)
return BRANCH_NAME
} catch (err) {
if (err.status === 404) {
console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`)
return 'main'
}
throw err
}
- name: Check out docs-early-access too, if internal repo
if: ${{ github.repository == 'github/docs-internal' }}
uses: actions/checkout@v3
with:
repository: github/docs-early-access
token: ${{ secrets.DOCUBOT_REPO_PAT }}
path: docs-early-access
ref: ${{ steps.check-early-access.outputs.result }}
- name: Merge docs-early-access repo's folders
if: ${{ github.repository == 'github/docs-internal' }}
run: |
mv docs-early-access/assets assets/images/early-access
mv docs-early-access/content content/early-access
mv docs-early-access/data data/early-access
rm -r docs-early-access
# This is necessary when LFS files where cloned but does nothing
# if actions/checkout was run with `lfs:false`.
- name: Checkout LFS objects
run: git lfs checkout
- name: Gather files changed
uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
id: get_diff_files
with:
# So that `steps.get_diff_files.outputs.files` becomes
# a string like `foo.js path/bar.md`
output: ' '
- name: Insight into changed files
run: |
# Must to do this because the list of files can be HUGE. Especially
# in a repo-sync when there are lots of translation files involved.
echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16.14.x
cache: npm
- name: Install dependencies
run: npm ci
- name: Cache nextjs build
uses: actions/cache@v3
with:
path: .next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}
- name: Run build script
run: npm run build
- name: Run tests
env:
DIFF_FILE: get_diff_files.txt
CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json
run: npm test -- tests/${{ matrix.test-group }}/
Understanding the example
The following table explains how each of these features are used when creating a GitHub Actions workflow.
Code | Explanation |
---|---|
name: Node.js Tests
|
The name of the workflow as it will appear in the "Actions" tab of the GitHub repository. |
on:
|
The |
workflow_dispatch:
|
Add the |
pull_request:
|
Add the |
push: branches: - main
|
Add the |
permissions: contents: read pull-requests: read
|
Modifies the default permissions granted to |
concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
|
Creates a concurrency group for specific events, and uses the |
cancel-in-progress: true
|
Cancels any currently running job or workflow in the same concurrency group. |
jobs:
|
Groups together all the jobs that run in the workflow file. |
test:
|
Defines a job with the ID |
runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}
|
Configures the job to run on a GitHub-hosted runner or a self-hosted runner, depending on the repository running the workflow. In this example, the job will run on a self-hosted runner if the repository is named |
timeout-minutes: 60
|
Sets the maximum number of minutes to let the job run before it is automatically canceled. For more information, see |
strategy:
|
This section defines the build matrix for your jobs. |
fail-fast: false
|
Setting |
matrix: test-group: [ content, graphql, meta, rendering, routing, unit, linting, translations, ]
|
Creates a matrix named |
steps:
|
Groups together all the steps that will run as part of the |
- name: Check out repo uses: actions/checkout@v3 with: lfs: ${{ matrix.test-group == 'content' }} persist-credentials: 'false'
|
The |
- name: Figure out which docs-early-access branch to checkout, if internal repo if: ${{ github.repository == 'github/docs-internal' }} id: check-early-access uses: actions/github-script@v6 env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: github-token: ${{ secrets.DOCUBOT_REPO_PAT }} result-encoding: string script: | // If being run from a PR, this becomes 'my-cool-branch'. // If run on main, with the `workflow_dispatch` action for // example, the value becomes 'main'. const { BRANCH_NAME } = process.env try { const response = await github.repos.getBranch({ owner: 'github', repo: 'docs-early-access', BRANCH_NAME, }) console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`) return BRANCH_NAME } catch (err) { if (err.status === 404) { console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`) return 'main' } throw err }
|
If the current repository is the |
- name: Check out docs-early-access too, if internal repo if: ${{ github.repository == 'github/docs-internal' }} uses: actions/checkout@v3 with: repository: github/docs-early-access token: ${{ secrets.DOCUBOT_REPO_PAT }} path: docs-early-access ref: ${{ steps.check-early-access.outputs.result }}
|
If the current repository is the |
- name: Merge docs-early-access repo's folders if: ${{ github.repository == 'github/docs-internal' }} run: | mv docs-early-access/assets assets/images/early-access mv docs-early-access/content content/early-access mv docs-early-access/data data/early-access rm -r docs-early-access
|
If the current repository is the |
- name: Checkout LFS objects run: git lfs checkout
|
This step runs a command to check out LFS objects from the repository. |
- name: Gather files changed uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b id: get_diff_files with: # So that `steps.get_diff_files.outputs.files` becomes # a string like `foo.js path/bar.md` output: ' '
|
This step uses the |
- name: Insight into changed files run: | echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt
|
This step runs a shell command that uses an output from the previous step to create a file containing the list of files changed in the pull request. |
- name: Setup node uses: actions/setup-node@v3 with: node-version: 16.14.x cache: npm
|
This step uses the |
- name: Install dependencies run: npm ci
|
This step runs the |
- name: Cache nextjs build uses: actions/cache@v3 with: path: .next/cache key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}
|
This step uses the |
- name: Run build script run: npm run build
|
This step runs the build script. |
- name: Run tests env: DIFF_FILE: get_diff_files.txt CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json run: npm test -- tests/${{ matrix.test-group }}/
|
This step runs the tests using |
Next steps
- To learn about GitHub Actions concepts, see "Understanding GitHub Actions."
- For more step-by-step guide for creating a basic workflow, see "Quickstart for GitHub Actions."
- If you're comfortable with the basics of GitHub Actions, you can learn about workflows and their features at "About workflows."