GitHub·8 min read·

Reusable Workflows: Sharing CI Logic Across Repositories

Factor repeated pipeline logic into reusable GitHub Actions workflows with typed inputs and secrets, and share one tested CI definition across repositories.

NB

Netbay Cloud Team

Netbay Engineering

On this page

Copy the same test pipeline into twenty repositories and you have twenty copies to maintain, and twenty chances to drift. GitHub Actions answers with reusable workflows: workflows that live in one repository and can be called from many others, as if they were actions. One definition, one place to fix a bug, and every caller updates. This post explains how to write, publish, and version reusable workflows, plus how to pass inputs and secrets safely.

What makes a workflow reusable

A reusable workflow is an ordinary workflow with three small differences. First, it declares triggers with on: workflow_call instead of push or schedule — workflow_call marks it as invocable from another workflow and prevents it from running on its own. Second, it exposes inputs and secrets with typed definitions. Third, it needs to be reachable from the caller, either in the same repository, a public repository, or an org with the right sharing settings.

Calling workflows is a separate file-level mechanism from composite actions. A reusable workflow wraps whole jobs and can span multiple environments; a composite action wraps a series of steps. For this post, focus on the workflow level.

The reusable workflow definition

Here is a reusable test workflow. It accepts a node-version input, a required secret, and decides whether to run coverage:

yaml
name: Reusable Test
on:
  workflow_call:
    inputs:
      node-version:
        required: true
        type: string
      run-coverage:
        required: false
        type: boolean
        default: true
    secrets:
      CODECOV_TOKEN:
        required: false

jobs:
  test:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci
      - run: npm test
      - if: inputs.run-coverage
        run: npx jest --coverage --coverageReporters=text
      - if: inputs.run-coverage && secrets.CODECOV_TOKEN != ''
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

Calling the reusable workflow

From a caller workflow, use the uses key with the reusable workflow reference, the with key for inputs, and the secrets key to forward secrets. The reference syntax is owner/repo/.github/workflows/file-name.yml@ref. Callers look like this:

yaml
name: CI
on: [push, pull_request]

jobs:
  test:
    uses: acme-org/shared-pipelines/.github/workflows/test.yml@v1
    with:
      node-version: 20
      run-coverage: true
    secrets:
      CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Note that reusable workflows and event triggers cannot both reference workflow_call in some combinations. A reusable workflow cannot itself be triggered by a push if it is meant to be called — and one useful limitation is that a called workflow cannot access the caller's environment-level secrets, only what you pass via the secrets key. Keep secrets forwarding explicit so you always know exactly what is crossing the boundary.

Versioning reusable workflows

Because uses supports a ref, you can pin a specific tag or SHA. The safe practice is to pin to a tag like v1, or better, a full commit SHA, and update deliberately. Tagging releases of your shared pipeline means callers do not unexpectedly break when you change the shared file. Update the tag pointer only after you have tested the new behavior in a few repos.

When not to reach for a reusable workflow

A reusable workflow is heavier than a composite action. It starts fresh jobs with their own runners and checkouts, so it costs setup time and cannot share the working tree with the caller. If you only need to factor out a handful of steps that must run inside the same job — checkout, install, cache, lint — a composite action is usually the better fit. Use reusable workflows when the unit of reuse is a whole pipeline phase: a test job, a build job, a deploy job that multiple repositories share.

Renaming note and gotchas

Two gotchas trip people up. First, environment: works inside reusable workflows, so a reusable deploy workflow can still target a protected environment in the caller's settings. Second, templates versus reusable workflows are different — a template is duplicated at design time for a new repository, while a reusable workflow is referenced live. If you edit a file and see other repos not picking it up, confirm they pin to a tag: update the tag or the caller's ref.

repo A repo B repo C shared test workflow inputs + secrets + version tag on: workflow_call one file, every caller updates

Takeaway

Reusable workflows let an entire team own CI once instead of twenty broken copies. Define inputs and secrets explicitly, pin callers to a version tag, and keep them for whole pipeline phases rather than small step groups. To exercise a shared deploy workflow against real infrastructure, create a dedicated Ubuntu VPS on Netbay in under a minute at netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance