You have 5 microservices with nearly identical pipelines. Reusable workflows let you define the pipeline ONCE and call it from each service — the architect's tool for scaling CI/CD across an organization.

The Problem: Copy-Paste Pipelines

Without: 5 Copies api/ci.yml (90 lines) web/ci.yml (88 lines) worker/ci.yml (91 lines) auth/ci.yml (89 lines) 90% identical! Change process? → Edit 5 files → Miss one? Drift. With: 1 Template + 5 Callers reusable-ci.yml (90 lines) api (3 lines) web (3 lines) worker (3 lines) Change process? → Edit 1 file All services update automatically ✓

How It Works: workflow_call

Caller Workflow uses: ./.github/workflows/ reusable-ci.yml inputs + secrets Reusable Workflow (on: workflow_call) Job: lint Job: test Job: build Returns: outputs (version, artifact-name)
The caller passes parameters in. The reusable workflow does the work. Outputs flow back.

🏋️ Create a Reusable CI Workflow

.github/workflows/reusable-ci.yml:

name: Reusable CI

on:
  workflow_call:
    # Parameters the caller provides
    inputs:
      node-version:
        type: string
        default: '20'
      working-directory:
        type: string
        default: '.'
      run-lint:
        type: boolean
        default: true
    outputs:
      version:
        description: 'Package version'
        value: ${{ jobs.build.outputs.version }}

jobs:
  lint:
    if: inputs.run-lint
    runs-on: ubuntu-latest
    defaults:
      run: { working-directory: ${{ inputs.working-directory }} }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ inputs.node-version }}, cache: 'npm' }
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    defaults:
      run: { working-directory: ${{ inputs.working-directory }} }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ inputs.node-version }}, cache: 'npm' }
      - run: npm ci
      - run: npm test

  build:
    needs: [lint, test]
    if: always() && !contains(needs.*.result, 'failure')
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.ver.outputs.value }}
    defaults:
      run: { working-directory: ${{ inputs.working-directory }} }
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ inputs.node-version }}, cache: 'npm' }
      - run: npm ci && npm run build
      - id: ver
        run: echo "value=$(node -p 'require(\"./package.json\").version')" >> $GITHUB_OUTPUT

Now call it from a service — .github/workflows/ci.yml:

name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  ci:
    uses: ./.github/workflows/reusable-ci.yml
    with:
      node-version: '20'
      working-directory: '.'

That's it. 3 lines to get a full CI pipeline.

Passing Secrets to Reusable Workflows

# In the reusable workflow definition:
on:
  workflow_call:
    secrets:
      AZURE_CLIENT_ID:
        required: true

# In the caller:
jobs:
  deploy:
    uses: ./.github/workflows/reusable-deploy.yml
    secrets:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
    # Or pass ALL secrets at once:
    secrets: inherit

Architect Decision: When to Use What

MechanismScopeUse When
Reusable WorkflowFull pipeline (multiple jobs)Standardize CI or CD across services
Composite ActionA sequence of stepsRepeated step pattern (setup, auth)
workflow_runChain workflows"After CI succeeds, start CD"

🧠 Recall Check

  1. What trigger does a reusable workflow use instead of push or pull_request?
  2. How does the caller provide different configuration to the same reusable workflow?
  3. How do you pass secrets from a caller to a reusable workflow?
  4. What's the key difference between reusable workflows and composite actions?
Reveal answers
  1. on: workflow_call — this declares "I can be called by other workflows."
  2. Via inputs: — the reusable workflow declares typed parameters, callers provide values with with:.
  3. Either explicitly (secrets: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}) or with secrets: inherit to pass all.
  4. Reusable workflows contain complete jobs (lint, test, build). Composite actions are a sequence of steps within a single job (checkout + setup + install).
Reusable workflows are how you scale CI/CD across an organization. Define best practices once, enforce them everywhere. When you update the template, all services get the improvement automatically. This is the platform engineering mindset.

Next lesson: Composite Actions — creating reusable step sequences (the other DRY mechanism).