Back to Blog
DevOps10 min readDecember 20, 2024

GitHub Actions: Advanced Patterns for Production CI/CD

Advanced GitHub Actions patterns for production pipelines: matrix builds, reusable workflows, caching strategies, OIDC for cloud auth, and optimising for speed and cost.

GitHub ActionsCI/CDDevOpsAutomationYAML
A

Azam

DevOps & AI Consultant

Beyond the Basic Pipeline

Most teams get GitHub Actions working quickly and then stop improving it. The result is a CI pipeline that takes 20 minutes, burns through expensive minutes, and has secrets copied across a dozen workflow files. The advanced patterns in this guide cut build times by 60–80%, eliminate secret duplication, and make pipelines maintainable as codebases grow.

Caching Dependencies Correctly

Dependency installation is often the slowest step in a pipeline. Use actions/cache with a hash of your lockfile as the cache key — the cache is invalidated when dependencies change and reused otherwise.

- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

- name: Install dependencies
  run: npm ci

For Python projects, cache the pip download directory rather than site-packages, since site-packages can vary by Python version and platform in ways that cause subtle cache poisoning bugs.

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}

Matrix Builds for Cross-Platform Testing

Test across multiple versions, operating systems, or configurations in parallel using a matrix strategy. All combinations run concurrently — no sequential overhead.

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: ['18', '20', '22']
        exclude:
          - os: windows-latest
            node: '18'  # skip this specific combination
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - run: npm test

Set fail-fast: false so a failure in one matrix combination does not cancel all others — you want to see the full failure picture, not just the first failure.

Reusable Workflows

When the same pipeline logic appears in multiple repositories, extract it into a reusable workflow. The calling workflow passes inputs; the reusable workflow does the work. This is the right pattern for shared deployment logic across a monorepo or across a fleet of microservices.

# .github/workflows/deploy-reusable.yml
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      service:
        required: true
        type: string
    secrets:
      AWS_ROLE_ARN:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: us-east-1
      - run: ./scripts/deploy.sh ${{ inputs.service }} ${{ inputs.environment }}
# Calling workflow
jobs:
  deploy:
    uses: ./.github/workflows/deploy-reusable.yml
    with:
      environment: production
      service: api
    secrets:
      AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}

OIDC for Keyless Cloud Authentication

Storing AWS or GCP credentials as GitHub secrets is a security risk — long-lived credentials that can be leaked. Use OIDC (OpenID Connect) instead: GitHub generates a short-lived token for each workflow run that your cloud provider can validate without any stored secret.

# Configure AWS IAM trust policy to accept GitHub OIDC tokens
# (one-time setup in AWS IAM)

# In your workflow — no long-lived credentials needed
jobs:
  deploy:
    permissions:
      id-token: write   # required for OIDC
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
          aws-region: us-east-1
          # No access key or secret key — OIDC handles it

OIDC tokens are valid for the duration of the workflow run only, have no expiry to rotate, and can be scoped to specific branches or environments in the IAM trust policy.

Concurrency Controls

By default, pushing multiple commits quickly queues up multiple deployments. Use concurrency groups to cancel superseded runs automatically.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # cancel older runs in the same branch

For deployment workflows, you may want cancel-in-progress: false so a deployment is never interrupted mid-flight — only queued-but-not-started runs are cancelled.

Speed Optimisation Checklist

  • Use actions/upload-artifact to pass build outputs between jobs instead of rebuilding in each job
  • Move slow checks (e2e tests, security scans) to a separate workflow triggered on PRs but not blocking merge for non-critical paths
  • Use paths filters to skip workflows when only unrelated files change (e.g. skip backend tests when only docs change)
  • Split a large test suite across parallel jobs using test sharding
  • Use larger runners (ubuntu-latest-4-cores) for compute-heavy jobs — the cost per minute is higher but total time and cost often drops

Want to Build This for Your Team?

I help teams implement the patterns and architectures described in these articles. Let's talk about your project.

Book a Free Call