C.W.K.
Stream
Lesson 03 of 14 · published

Reusable Workflows

~13 min · reusable, workflow_call, centralization

Level 0Apprentice
0 XP0/101 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

One workflow, many callers

A reusable workflow is a workflow file with on: workflow_call. Other workflows reference it via uses:. The called workflow runs as its own job, with its own runner, but inherits secrets and variables from the caller.

Why this is the centralization mechanism

If 12 repos all need to do the same 'build, test, push to GHCR, smoke deploy' sequence, you don't want to maintain 12 copies. Put the workflow in a single repo (org/.github or org/ci), expose it as workflow_call, and let every consumer point at it.

Inputs, outputs, secrets

  • inputs — typed parameters from the caller (string, boolean, number).
  • outputs — values produced by the called workflow, readable by the caller's downstream jobs.
  • secrets — explicitly inherited or named one-by-one. Use secrets: inherit for the simple case.

Limitations to know

  • Only 4 levels of nesting (a calls b calls c calls d).
  • Cannot call a reusable workflow from a step (only from a job).
  • The called workflow's permissions: block applies, not the caller's.

Code

Reusable workflow definition·yaml
# .github/workflows/reusable-test.yml
name: reusable-test
on:
  workflow_call:
    inputs:
      python-version:
        type: string
        default: '3.12'
      coverage-min:
        type: number
        default: 80
    outputs:
      coverage:
        description: Reported coverage percentage
        value: ${{ jobs.test.outputs.coverage }}

jobs:
  test:
    runs-on: ubuntu-latest
    outputs:
      coverage: ${{ steps.cov.outputs.value }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '${{ inputs.python-version }}' }
      - run: pip install -e '.[dev]' && pytest --cov=src --cov-fail-under=${{ inputs.coverage-min }}
      - id: cov
        run: echo "value=$(coverage report | tail -1 | awk '{print $4}')" >> $GITHUB_OUTPUT
Caller workflow·yaml
# .github/workflows/ci.yml
name: ci
on: { pull_request: {}, push: { branches: [main] } }

jobs:
  call-test:
    uses: ./.github/workflows/reusable-test.yml   # local repo
    # or: uses: my-org/ci-workflows/.github/workflows/reusable-test.yml@v1
    with:
      python-version: '3.12'
      coverage-min: 85
    secrets: inherit

  use-output:
    needs: call-test
    runs-on: ubuntu-latest
    steps:
      - run: echo "Coverage was ${{ needs.call-test.outputs.coverage }}%"

External links

Exercise

Identify two repos that have nearly-identical CI workflow files. Hoist the shared shape into a reusable workflow in a third repo. Both consumers now uses: the reusable one and stay in sync with one update.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.