GitHub Actions Tutorial — Composition Patterns

2 minute read

Composition Patterns

Reusable workflows become powerful when composed together into pipelines. Here are proven patterns for building maintainable CI/CD systems.


Pattern 1 — Linear Pipeline

Chain reusable workflows sequentially with needs::


name: CI/CD Pipeline

on:
  push:
    branches: [main]

jobs:
  lint:
    uses: ./.github/workflows/lint.yml

  test:
    needs: lint
    uses: ./.github/workflows/test.yml
    with:
      node-version: '20'

  build:
    needs: test
    uses: ./.github/workflows/build.yml
    secrets: inherit

  deploy:
    needs: build
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets: inherit

lint → test → build → deploy

Each stage must pass before the next starts.


Pattern 2 — Fan-Out / Fan-In

Run multiple workflows in parallel, then converge:


name: CI

on: push

jobs:
  lint:
    uses: ./.github/workflows/lint.yml

  unit-test:
    uses: ./.github/workflows/unit-test.yml

  integration-test:
    uses: ./.github/workflows/integration-test.yml

  build:
    needs: [lint, unit-test, integration-test]      # fan-in
    uses: ./.github/workflows/build.yml

       ┌── lint ──────────┐
push ──┼── unit-test ─────┼── build
       └── integration ───┘

All three checks run in parallel. build waits for all three.


Pattern 3 — Environment Promotion

Progressive deployment through environments:


name: Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    uses: ./.github/workflows/build.yml
    secrets: inherit

  deploy-dev:
    needs: build
    uses: ./.github/workflows/deploy.yml
    with:
      environment: dev
    secrets: inherit

  deploy-staging:
    needs: deploy-dev
    uses: ./.github/workflows/deploy.yml
    with:
      environment: staging
    secrets: inherit

  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets: inherit

build → deploy-dev → deploy-staging → deploy-production

The same deploy.yml is called three times with different inputs.


Pattern 4 — Matrix + Reusable

Call a reusable workflow for each matrix combination:


name: Cross-Platform

on: push

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    uses: ./.github/workflows/test.yml
    with:
      runner: ${{ matrix.os }}

This creates 3 calls to the reusable workflow, each with a different runner.


Pattern 5 — Shared CI Template

One reusable workflow serves as a template for all services in a monorepo:


# .github/workflows/service-ci.yml (reusable)
on:
  workflow_call:
    inputs:
      service-name:
        type: string
        required: true
      working-directory:
        type: string
        required: true

jobs:
  ci:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ inputs.working-directory }}
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: ${{ inputs.service-name }}-build
          path: ${{ inputs.working-directory }}/dist


# .github/workflows/ci.yml (caller)
on:
  push:
    branches: [main]

jobs:
  api:
    uses: ./.github/workflows/service-ci.yml
    with:
      service-name: api
      working-directory: services/api

  web:
    uses: ./.github/workflows/service-ci.yml
    with:
      service-name: web
      working-directory: services/web

  auth:
    uses: ./.github/workflows/service-ci.yml
    with:
      service-name: auth
      working-directory: services/auth

Three services, same CI pipeline, different directories.


Common Mistakes

Mistake Fix
Circular dependencies Reusable workflows can’t call themselves (directly or indirectly)
Too many nested calls Keep nesting under 4 levels; flatten when possible
Duplicating logic in callers Extract shared logic into a reusable workflow
No version pinning Use tags or SHAs for cross-repo reusable workflows

Quick Reference

Pattern Structure Use Case
Linear A → B → C Sequential pipeline
Fan-out/in A ⟶ [B,C,D] → E Parallel checks
Promotion Same workflow × N envs Environment rollout
Matrix + reusable Matrix calls reusable Cross-platform
Template One workflow, many callers Monorepo CI

← Previous: Calling Workflows

Next: Org Shared Workflows →

Updated: