Cartoon gears and neon rolling marbles rolling through a track

Create reusable workflows in GitHub Actions

Bekah Whittle
Bekah Whittle // Director, Field Services // GitHub

Reusable workflows allow you to embrace the DRY (Don’t Repeat Yourself) principle and usher in standardization and consistently implement best practices. They smooth developer journeys, making processes more streamlined and efficient, without imposing undue restrictions. As we move along, we will be joined by Deutsche Vermögensberatung AG (DVAG) for their advice on getting the most out of reusable workflows.


In this guide, you will learn:

  • The uses and benefits of reusable workflows

  • The essential components and syntax required to create reusable workflows

  • The URL structure and syntax for invoking a reusable workflow

  • How to incorporate reusable workflows into projects housed in separate repositories


What are reusable workflows and how do they work?

A reusable workflow is a GitHub Actions file that can be executed by other workflows. A single workflow can call multiple reusable workflows, with each reference taking up just one line of YAML. This means that the “caller” workflow may contain just a few lines of YAML, but perform a large number of tasks, since it runs each “called” workflow entirely, as if its jobs and steps were part of its own. Additionally, workflows can be nested up to a maximum of four levels.

What are the benefits of reusable workflows?

Reusable workflows offer several benefits:

  • Avoid redundancy

  • Speed up workflow creation through reuse

  • Reduce maintenance and enhance security by offering a library of reusable workflows that can be centrally maintained

  • Promote the use of workflows that are well-designed, already tested, and proven effective

With these benefits in mind, and in combination with runner groups, organizations can create paved paths for developers that allow them to spend time focusing on the work at hand, rather than managing infrastructure or recreating existing workflows.

We centralize our workflows in a monorepo where all changes require code-owner approval to ensure consistency and quality over time, while also enabling specialized ownership for certain workflows. We use semantic versioning and avoid breaking changes by sticking to a 1.x.x version scheme. We generally release new workflow versions weekly, using auto-merge functionality to keep updates flowing and  testing them on both the main branch and on test repos. We also actively monitor workflow use across repositories, so we can ensure they’re being used effectively.

Florian Koch
Florian Koch // IT Platform Lead // Deutsche Vermögensberatung (DVAG)

Components of a reusable workflow

The triggering event

For a workflow to be reusable, the values for on must include workflow_call:

on:
  workflow_call:

Input definition

You can define inputs and secrets in a reusable workflow to receive them from a caller workflow. Use the inputs and secrets keywords for this purpose. For more information, see our guide on securing CI/CD with secrets and variables. Below is a commented version of an example reusable workflow:

on:  # Specifies the event triggering the workflow
  workflow_call:  # Indicates that this is a reusable workflow
    inputs:  # Defines the inputs that can be passed from the caller workflow
      config-path:  # Name of the input
        required: true  # Specifies that this input is mandatory
        type: string  # Specifies the type of the input
    secrets:  # Defines the secrets that can be passed from the caller workflow
      envPAT:  # Name of the secret
        required: true  # Specifies that this secret is mandatory

In the example above, envPAT is an environment secret that's been added to the production environment. This environment is therefore referenced within the job.

Passing named secrets

To pass named inputs to a called workflow, use the with keyword in a job. Use the secrets keyword to pass named secrets. For inputs, the data type of the input value must match the type specified in the called workflow (either boolean, number, or string).

jobs:
  call-workflow-passing-data:
    uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main
    with:
      config-path: .github/labeler.yml
    secrets:
      envPAT: ${{ secrets.envPAT }}

Workflows that call reusable workflows in the same organization or enterprise can use the inherit keyword to implicitly pass the secrets.

jobs:
  call-workflow-passing-data:
    uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main
    with:
      config-path: .github/labeler.yml
    secrets: inherit

We rely on Dependabot to automatically distribute updates of our reusable workflows across teams and projects. Each release automatically triggers a pull request in the user repositories, which we closely track in a dedicated issue. This lets us keep tabs on who has and hasn’t updated their workflows and gives us a streamlined way to ensure everyone is on the same page, using the most current and effective workflows.

Florian Koch
Florian Koch // IT Platform Lead // Deutsche Vermögensberatung (DVAG)

Calling reusable Actions

You call a reusable workflow by using the uses keyword. Unlike when you are using Actions within a workflow, you call reusable workflows directly within a job, and not from within job steps. Let's break down the URL structure for calling a reusable workflow. In the example given above, you’ll see the line:

uses: octo-org/example-repo/.github/workflows/reusable-workflow.yml@main

We can see that the code follows a specific structure:

uses: [OWNER/REPOSITORY PATH]/[.github/workflows/WORKFLOW_FILE]@[REF]

Each segment of this structure has a specific purpose:

1. uses:

This is a keyword in the Actions syntax that indicates the following string will reference a reusable piece of code, such as an Action or, in this case, a reusable workflow.

2. OWNER:

This specifies the GitHub user or organization that owns the repository where the reusable workflow is located (ex: octo-org).

3. REPOSITORY PATH:

This is the path to the repository (ex: example-repo/).

4. .github/workflows/:

This is a conventional path where Actions expects to find workflow definitions in a repository. All workflow YAML files should be located within this directory for them to be recognized and executed by Actions.

5. WORKFLOW_FILE:

This specifies the exact YAML file that contains the workflow definition. (ex: reusable-workflow.yml).

6. @REF:

This indicates the branch, tag, or commit SHA from which the workflow should be used. It's essential to specify this so that Actions knows which version of the workflow to use. This is particularly useful if workflows evolve over time, and you want to ensure a specific version is used in your own workflow (ex: @main).

How to make a workflow reusable

Here’s an example from the build-test-deploy.yml workflow we created in the Automation Essentials module. Below we updated the file to accommodate reusability. In addition to adding workflow_call, we also defined parameters allowing us to set the node-version. Pay attention to the comments outlined in the code:

name: build-test-deploy
# Changed 'on' to enable this workflow to be called from other workflows
on:
  workflow_call:
    # Introduced 'inputs' to define parameters that can be passed when calling this workflow
    inputs:
      node-version:
        description: "Node version"
        required: true
        type: string

jobs: 
  build:
    runs-on: ubuntu-latest

    steps: 
      - name: checkout repo
        uses: actions/checkout@v3
      # Modified to use the node-version from the workflow inputs
      - name: use node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ inputs.node-version }}

      - run: npm install
      - run: npm run build

  test: 
    needs: build

    runs-on: ubuntu-latest

    steps: 
      - name: checkout repo
        uses: actions/checkout@v3
      # Modified to use the node-version from the workflow inputs
      - name: use node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ inputs.node-version }}
          
      - run: npm install
      - run: npm run test

Now that we have updated our build-test-deploy workflow to accommodate reusability and we understand how to call a reusable workflow, let's put it all together. In the example code below, we call the build-test-deploy workflow from a workflow located in a separate repository:

jobs:
  my-job:
    # Importing a reusable workflow from another repository and branch
    uses: organization/repo/.github/workflows/build-test-deploy.yml@main
    # Passing 'node-version' as an input parameter to the reusable workflow
    with:
      node-version: '18.x'

Up next: Manage and monitor workflows in GitHub Actions

Ready to optimize how you manage and monitor your Actions? Check out our in-depth guide. From analyzing workflow statuses to tracking resource usage, we'll arm you with the tools you need to operate more efficiently.