Reusable workflows
Our final advanced use case we would like to cover is the use of reusable workflows.
Suppose you're in a monorepo and have the following sub-projects:
web
admin
api
All of them require the exact same CI pipeline of running unit tests and linting that we introduced in Basics of Github Actions. If you copy-pasted the same workflow file three times, it might work, but this means that if one changes, everything needs to change. While some might argue that as the application expands, this flexibility is required to avoid a tight coupling to one type of workflow. However, for the sake of simplicity, let's suppose that this duplication is fundamentally bad for this use case. How do we go about reconciling this?
Well, this is where reusable workflows come in. They allow you to effectively define a "common workflow" that can be shared and reused by other workflows as steps. Essentially, what you're creating is custom actions that have not been properly published.
The official documentation goes into the nitty gritty of the limitations and access of reusable workflows, so we will not cover it in this section. Instead, we will focus on setting up a very rudimentary reusable workflow for the above scenario.
So let's suppose that the original workflow looks like this:
# .github/workflows/web_ci.yml
name: CI/CD Pipeline
on: [pull_request, workflow_dispatch]
jobs:
linting:
runs-on: ubuntu-latest
steps:
- name: Fetch repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- name: Install dependencies
run: |
yarn
- name: Lint code
run: |
yarn lint
unit-tests:
runs-on: ubuntu-latest
steps:
- name: Fetch repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- name: Install dependencies
run: |
yarn
- name: Run unit tests
run: |
NODE_ENV=production yarn test
You realize that the job steps are exactly identical, apart from the folder that these commands are being run in. We can generalize these as inputs to the reusable workflow!
# .github/workflows/reusable-ci.yml
name: Reusable CI Workflow
on:
workflow_call:
inputs:
workdir:
description: 'Working directory'
required: true
type: string
jobs:
linting:
runs-on: ubuntu-latest
steps:
- name: Fetch repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'yarn'
cache-dependency-path: "${{ inputs.workdir }}/yarn.lock"
- name: Install dependencies
working-directory: ${{ inputs.workdir }}
run: |
yarn
- name: Lint code
working-directory: ${{ inputs.workdir }}
run: |
yarn lint
unit-tests:
runs-on: ubuntu-latest
steps:
- name: Fetch repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'yarn'
cache-dependency-path: "${{ inputs.workdir }}/yarn.lock"
- name: Install dependencies
working-directory: ${{ inputs.workdir }}
run: |
yarn
- name: Run unit tests
working-directory: ${{ inputs.workdir }}
run: |
NODE_ENV=production yarn test
Essentially, the key things that had to alter were:
Changing the trigger event type to
workflow_call
, indicating it's a reusable workflowSpecifying the inputs that the reusable workflow requires, such as the
workdir
since that is the only thing that changes across variations of this CI workflowSpecifying the
cache-dependency-path
in theactions/setup-node@v4
action as we need to use theyarn.lock
files specific to each sub-projectSpecifying the
working-directory
of each step to point to the given sub-project directory
This is all we really need to create the reusable workflow. Then, we can update our original ci.yml
with the following:
# .github/workflows/web_ci.yml
name: CI/CD Pipeline
on: [pull_request, workflow_dispatch]
jobs:
ci:
uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
with:
workdir: web
In fact, we can even inline every sub-project's CI into the same workflow:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on: [pull_request, workflow_dispatch]
jobs:
web_ci:
uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
with:
workdir: web
admin_ci:
uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
with:
workdir: admin
api_ci:
uses: <org name/username>/<repo name>/.github/workflows/reusable-ci.yml@main
with:
workdir: api
Incredible, we've managed to greatly simplify our CI workflow by using reusable workflows!
Last updated