Skip to content

GitHub Actions Advanced — Matrix Builds, Caching, Reusable Workflows, and Deployment Patterns

DodaTech Updated 2026-06-22 9 min read

In this tutorial, you'll learn about GitHub Actions Advanced. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

GitHub Actions advanced patterns enable teams to build efficient, maintainable CI/CD pipelines using matrix strategies for parallel testing, intelligent caching to reduce build times, reusable workflows to eliminate duplication, and OIDC-based cloud authentication for secure deployments.

What You'll Learn

Why It Matters

Basic GitHub Actions workflows copy-pasted across repositories lead to configuration drift, wasted CI minutes from redundant builds, and security risks from long-lived cloud credentials stored as secrets. Advanced patterns reduce pipeline runtime by 60%, eliminate duplicate workflow code across dozens of repositories, and replace static cloud credentials with short-lived OIDC tokens that auto-rotate. These patterns are essential for organizations scaling from one Repository to hundreds.

Real-World Use

DodaTech maintains a single reusable deployment workflow consumed by 45 repositories. Matrix builds test Durga Antivirus Pro components across 3 operating systems and 5 Python versions in under 12 minutes using intelligent caching. OIDC authentication to AWS eliminates manual credential rotation for 200+ deployment environments.

flowchart TD
    A["Caller Workflow"] -->|"uses: org/deploy-workflow@v1"| B["Reusable Workflow"]
    B --> C["Matrix: OS x Python"]
    C --> D["Ubuntu / Python 3.11"]
    C --> E["Ubuntu / Python 3.12"]
    C --> F["macOS / Python 3.11"]
    C --> G["Windows / Python 3.11"]
    D --> H["Restore Cache"]
    H --> I["Run Tests"]
    I --> J["Upload Artifacts"]
    J --> K["Build Docker Image"]
    K --> L["Scan + Push"]
    L --> M["Deploy to Environment"]
    M --> N["production"]
    M --> O["staging"]
    style A fill:#2088FF,color:#fff
    style B fill:#2088FF,color:#fff
    style C fill:#326CE5,color:#fff
â„šī¸ Info

Prerequisites: Working knowledge of GitHub Actions (jobs, steps, triggers), a GitHub repository, and basic Docker and CI/CD concepts.

Matrix Builds for Parallel Testing

Matrix builds test across multiple operating systems, language versions, and dependency configurations simultaneously.

# .github/workflows/matrix-test.yml
name: Matrix Tests
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ["3.10", "3.11", "3.12"]
        exclude:
          - os: windows-latest
            python-version: "3.10"
        include:
          - os: ubuntu-latest
            python-version: "3.12"
            coverage: true
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies
        run: pip install -r requirements-dev.txt
      - name: Run tests
        run: pytest tests/ -v --junitxml=results.xml
      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.os }}-${{ matrix.python-version }}
          path: results.xml

Expected behavior: GitHub Actions creates 8 parallel jobs (3 OS x 3 Python minus 1 exclusion plus 1 inclusion with coverage flag). The exclude block skips Windows + Python 3.10. The include block adds a coverage: true variable to the Ubuntu 3.12 job for running coverage reports.

Intelligent Caching Strategies

Cache Python packages, Docker layers, and Node modules to reduce workflow execution time.

# .github/workflows/caching.yml
name: Build with Caching
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Cache Python packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('requirements*.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-

      - name: Cache Docker layers
        uses: actions/cache@v4
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-docker-${{ hashFiles('Dockerfile') }}-${{ hashFiles('requirements*.txt') }}
          restore-keys: |
            ${{ runner.os }}-docker-

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
tags: myapp:latest

      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

Expected behavior: The cache key uses a hash of the dependency files. If requirements.txt has not changed, the cache is restored and pip install takes seconds instead of minutes. Docker caching preserves build layers between runs, so unchanged layers are reused instead of rebuilt.

Caching Strategy Key Pattern Restore Keys Typical Savings
Minimal Hash of lockfile only None 30-50%
Fallback Hash of lockfile Partial key prefix 50-70%
Segmented Hash per dependency type Branch-level key 60-80%

Reusable Workflows

Define a workflow once and call it from any number of repositories, eliminating CI/CD code duplication.

# .github/workflows/deploy-reusable.yml (called workflow)
name: Deploy Application
on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
    secrets:
      AWS_ROLE_ARN:
        required: true
    outputs:
      deployment-url:
        description: "The URL of the deployed application"
        value: ${{ jobs.deploy.outputs.url }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    permissions:
      id-token: write
      contents: read
    outputs:
      url: ${{ steps.deploy-app.outputs.url }}
    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
          role-session-name: github-deploy-${{ inputs.environment }}

      - name: Deploy to ECS
        id: deploy-app
        run: |
          aws ecs update-service \
            --cluster ${{ inputs.environment }}-cluster \
            --service api-service \
            --force-new-deployment \
            --image ${{ secrets.ECR_REPOSITORY }}:${{ inputs.image-tag }}
          echo "url=https://${{ inputs.environment }}.api.dodatech.com" >> $GITHUB_OUTPUT

      - name: Verify deployment
        run: |
          aws ecs wait services-stable \
            --cluster ${{ inputs.environment }}-cluster \
            --services api-service
# .github/workflows/pipeline.yml (caller workflow)
name: Full Pipeline
on:
  push:
    branches: [main]

jobs:
  # ... test and build jobs omitted for brevity ...

  deploy-staging:
    needs: [build]
    uses: dodatech/deploy-actions/.github/workflows/deploy-reusable.yml@v1
    with:
      environment: staging
      image-tag: ${{ github.sha }}
    secrets:
      AWS_ROLE_ARN: ${{ secrets.AWS_STAGING_ROLE_ARN }}

  deploy-production:
    needs: [build, deploy-staging]
    uses: dodatech/deploy-actions/.github/workflows/deploy-reusable.yml@v1
    with:
      environment: production
      image-tag: ${{ github.sha }}
    secrets:
      AWS_ROLE_ARN: ${{ secrets.AWS_PROD_ROLE_ARN }}
    environment: production

OIDC Authentication for Cloud Providers

Replace long-lived cloud credentials with short-lived tokens using OpenID Connect.

# .github/workflows/oidc-auth.yml
name: OIDC AWS Deployment
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubOIDCRole
          aws-region: us-east-1

      - name: List S3 buckets (test access)
        run: aws s3 ls

Expected behavior: GitHub requests a token from AWS using OIDC. The token is valid for 15 minutes and is tied to the specific workflow run. No static AWS access keys are stored anywhere. AWS IAM trusts the GitHub OIDC provider and maps it to an IAM role with least-privilege permissions.

Common Errors

  1. Cache key too specific (includes branch name): A cache key like ${{ runner.os }}-${{ github.ref }}-${{ hashFiles('**/lockfile') }} creates one cache per branch, defeating the purpose. Use restore-keys with the runner OS prefix to fall back to the default branch's cache.

  2. Matrix explosion: A matrix with 5 OS, 4 Python versions, and 3 dependency configs creates 60 jobs. GitHub Actions has a limit of 256 jobs per workflow. Use exclude, include, and max-parallel to keep the matrix manageable.

  3. Reusable workflow versioning without tags: Referencing a reusable workflow with @main means every workflow run uses the latest code from the main branch, which could change without notice. Pin to a Git tag (@v1) or commit SHA (@abc123).

  4. Secrets not accessible in reusable workflows: Reusable workflows receive secrets only through the secrets: inherit keyword or explicit mapping. Directly accessing ${{ secrets.MY_SECRET }} in a called workflow without passing it results in an empty value.

  5. Environment protection rules blocking deployments: Deployment jobs targeting an environment with required_reviewers or wait_timer protection rules are blocked until an approver approves or the timer expires. Without this awareness, teams are confused why deployments hang for 30 minutes.

Practice Questions

  1. How does the matrix strategy include keyword differ from directly listing combinations? Answer: include adds new combinations to the matrix that are not generated by the Cartesian product. It is also used to add additional variables to specific existing combinations (like coverage: true for the Ubuntu 3.12 job). Direct listing requires enumerating every combination explicitly.

  2. Why is OIDC authentication more secure than storing AWS keys as secrets? Answer: OIDC tokens are short-lived (15 minutes), tied to a specific workflow run, and auto-rotate without manual rotation. Stored secrets are long-lived, can be leaked through logs or artifact uploads, and require manual rotation that is often forgotten.

  3. What happens when a reusable workflow needs to return data to the caller? Answer: Reusable workflows use outputs defined at the job level and mapped at the workflow_call level. The caller accesses them via needs.<job_id>.outputs.<output_name>. Outputs must be strings -- objects and arrays are not supported.

  4. How does Docker layer caching interact with BuildKit's --cache-from and --cache-to? Answer: cache-to exports the final build cache to a location (local filesystem, registry, S3) after the build. cache-from imports a previously exported cache before the build. The mode=max parameter saves all layers (not just the final stage), enabling maximum reuse across builds.

Challenge

Build a complete CI/CD system using advanced GitHub Actions patterns: create a reusable workflow deploy-app.yml that accepts environment, image-tag, and region inputs and uses OIDC authentication to deploy to AWS ECS Fargate with zero-downtime deployments, implement a caller workflow in another Repository that uses a matrix strategy to build Docker images for 3 services (api, worker, frontend) with parallel test execution across Node 18, 20, and 22, cache node_modules using the actions/cache action with a hash of package-lock.json, and chain the jobs so tests pass before building, building succeeds before deploying to staging, and staging passes before deploying to production with manual approval.

Mini Project

Build a reusable GitHub Actions platform for an organization with 20+ Microservices: create two reusable workflows (ci.yml for test/build, cd.yml for deploy), define a matrix strategy that covers Node.js 18/20/22 and 3 operating systems with exclusions for unsupported combinations, implement Docker layer caching with BuildKit that reduces build times by 70% across runs, configure OIDC authentication for deployment to AWS and GCP, add deployment verification steps (smoke tests, canary analysis), integrate status checks with deployment environments (staging approval, production approval with wait timer), create a composite action for setting up the monorepo tooling that is shared across workflows, publish the reusable workflows to a central .github Repository with semantic version tags, and write comprehensive documentation with Migration guides for teams to adopt the shared workflows.

Resource Description
GitHub Actions Basics Foundation workflows
CI/CD Best Practices Pipeline Design Patterns
Docker Multistage Builds Optimizing Docker builds
DevSecOps Pipeline Security scanning in CI/CD

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro