GitHub Actions Tutorial — Advanced Patterns

1 minute read

Advanced Patterns

These patterns go beyond standard CI/CD — they handle real-world complexity like monorepos, dynamic pipelines, and cross-repository coordination.


Monorepo — Path-Based Triggers

Only build what changed:


name: Monorepo CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      api: ${{ steps.filter.outputs.api }}
      web: ${{ steps.filter.outputs.web }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            api:
              - 'packages/api/**'
            web:
              - 'packages/web/**'

  build-api:
    needs: detect-changes
    if: needs.detect-changes.outputs.api == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cd packages/api && npm ci && npm test

  build-web:
    needs: detect-changes
    if: needs.detect-changes.outputs.web == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cd packages/web && npm ci && npm test

Only build-api runs if only packages/api/ changed. Only build-web runs if only packages/web/ changed.


Dynamic Matrix from JSON

Generate matrix values at runtime:


jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set.outputs.matrix }}
    steps:
      - uses: actions/checkout@v4
      - name: Generate matrix
        id: set
        run: |
          # Read from a config file or compute dynamically
          MATRIX=$(cat matrix.json)
          echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"

  build:
    needs: setup
    strategy:
      matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building ${{ matrix.project }} with ${{ matrix.node }}"

matrix.json:

{
  "include": [
    { "project": "api", "node": "20" },
    { "project": "web", "node": "18" },
    { "project": "cli", "node": "20" }
  ]
}

Cross-Repository Dispatch

Trigger a workflow in another repository:


jobs:
  notify:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger deploy repo
        uses: peter-evans/repository-dispatch@v3
        with:
          token: ${{ secrets.PAT_TOKEN }}
          repository: my-org/deploy-service
          event-type: app-updated
          client-payload: '{"version": "${{ github.sha }}"}'

The target repository listens for the event:


on:
  repository_dispatch:
    types: [app-updated]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying version ${{ github.event.client_payload.version }}"


Concurrency Control

Prevent duplicate runs:


concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

  • Pushes to the same branch cancel previous runs
  • Only the latest commit gets a full run
  • Saves runner minutes on frequent pushes

Conditional Deployment

Deploy only when specific conditions are met:


jobs:
  deploy:
    if: |
      github.ref == 'refs/heads/main' &&
      github.event_name == 'push' &&
      !contains(github.event.head_commit.message, '[skip deploy]')
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying..."


Quick Reference

Pattern Use Case
Path filters Monorepo — build only changed packages
Dynamic matrix Runtime-determined build configurations
Repository dispatch Cross-repo coordination
Concurrency Cancel duplicate runs
Conditional deploy Skip deploy on certain commits

← Back to: Chapter 16: Next Steps

Next: GitHub Apps →

Updated: