GitHub Actions: CI/CD Workflows From First Run to Secure Deployments · Pipelines, Deployments and Security

Reusable Workflows and Composite Actions

Remove duplication with workflow_call reusable workflows and your own composite actions.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A reusable test workflow with an input, called twice with different Node.js versions, plus a composite action that bundles setup steps.

The idea

Copy-pasted YAML drifts out of date. GitHub offers two ways to share:

  • A reusable workflow is a workflow with the workflow_call trigger. It declares typed inputs, secrets and outputs, and callers use it as a job with uses: ./.github/workflows/test.yml (or owner/repo/.github/workflows/test.yml@ref). A caller job has no steps or runs-on.
  • A composite action is an action.yml whose runs.using is composite; it bundles steps that run inside the caller's job. Each run step needs a shell:.

Choose a reusable workflow to share whole jobs (their own runners, secrets and environments) and a composite action to share a few steps.

The code

The reusable workflow:

A reusable test workflow (.github/workflows/test.yml)
name: Reusable test
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: "22"
    outputs:
      result:
        description: Test result
        value: ${{ jobs.test.outputs.result }}

jobs:
  test:
    runs-on: ubuntu-latest
    outputs:
      result: ${{ steps.done.outputs.result }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: npm
      - run: npm ci && npm test
      - id: done
        run: echo "result=passed" >> "$GITHUB_OUTPUT"

Output

actionlint: no problems found
workflow: Reusable test
triggers: workflow_call
jobs, in the order they run:
  test: runs on ubuntu-latest | 4 step(s)

Run this on your computer — see “Get Started” for setup.

The caller workflow, which runs it for two Node.js versions:

Calling the reusable workflow twice
name: CI
on: [push, pull_request]

jobs:
  node-22:
    uses: ./.github/workflows/test.yml
    with:
      node-version: "22"
  node-24:
    uses: ./.github/workflows/test.yml
    with:
      node-version: "24"
  summary:
    needs: [node-22, node-24]
    runs-on: ubuntu-latest
    steps:
      - run: echo "Results ${{ needs.node-22.outputs.result }} and ${{ needs.node-24.outputs.result }}"

Output

actionlint: no problems found
workflow: CI
triggers: push, pull_request
jobs, in the order they run:
  node-22: runs on (reusable workflow) | calls ./.github/workflows/test.yml
  node-24: runs on (reusable workflow) | calls ./.github/workflows/test.yml
  summary: runs on ubuntu-latest | 1 step(s) | after node-22, node-24

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Adding steps to the caller job. A job that uses a reusable workflow can't have steps.
  • Secrets not passed. A reusable workflow gets secrets only if you pass them with secrets: or secrets: inherit.
  • Forgetting shell: in a composite action. Every run step there needs one.
  • Unpinned references to other repositories. Pin @v1 or a SHA so shared workflows can't change under you.

Key points

  • workflow_call makes a workflow reusable; callers use it as a job.
  • Composite actions bundle steps; reusable workflows bundle jobs.
  • Pass inputs with with: and secrets with secrets:. Further reading: GitHub Docs.

Practice questions

When would you prefer a composite action to a reusable workflow?

Answer

When you want to share a handful of steps that run inside the caller's existing job, runner and workspace. Reusable workflows are better for whole jobs with their own runners, secrets and environments.

Why can't the caller job in the example have a runs-on key?

Answer

A job that calls a reusable workflow delegates execution to it; the called workflow's jobs define their own runners, so the caller can only set uses, with, secrets and needs.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?