Claude Code for GitHub Actions: Advanced Workflows, Matrix Builds, and Automation — Claude Skills 360 Blog
Blog / DevOps / Claude Code for GitHub Actions: Advanced Workflows, Matrix Builds, and Automation
DevOps

Claude Code for GitHub Actions: Advanced Workflows, Matrix Builds, and Automation

Published: June 16, 2026
Read time: 8 min read
By: Claude Skills 360

GitHub Actions’ YAML syntax is straightforward for basic pipelines but has subtleties that waste hours: expression syntax, concurrency groups, matrix strategies, reusable workflows, and the correct way to pass secrets between jobs. Claude Code generates GitHub Actions workflows with working syntax and patterns that avoid the common gotchas.

This guide covers advanced GitHub Actions with Claude Code: matrix builds, reusable workflows, environment promotion, secrets management, and cost optimization.

Advanced Matrix Builds

Run tests against Node.js 18, 20, and 22. Also test against
PostgreSQL 14 and 15 as a matrix. Only run the matrix on main branch PRs.
# .github/workflows/test-matrix.yml
name: Test Matrix

on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      fail-fast: false  # Don't cancel other matrix jobs when one fails
      matrix:
        node-version: ['18', '20', '22']
        postgres-version: ['14', '15']
        # Exclude combinations that would be redundant
        exclude:
          - node-version: '18'
            postgres-version: '14'

    services:
      postgres:
        image: postgres:${{ matrix.postgres-version }}
        env:
          POSTGRES_PASSWORD: testpassword
          POSTGRES_DB: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - run: npm ci
      
      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgres://postgres:testpassword@localhost:5432/test
          NODE_VERSION: ${{ matrix.node-version }}
          PG_VERSION: ${{ matrix.postgres-version }}

Reusable Workflows

We have 8 repositories that all need the same deploy workflow.
Create a reusable workflow that they can all call.
# .github/workflows/deploy-reusable.yml
name: Deploy (reusable)

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string     # 'staging' | 'production'
      app-name:
        required: true
        type: string
    secrets:
      CLOUDFLARE_API_TOKEN:
        required: true
      AWS_ROLE_ARN:
        required: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}  # Uses GitHub Environment protection rules
    
    permissions:
      id-token: write   # Required for OIDC
      contents: read

    steps:
      - uses: actions/checkout@v4
      
      - name: Configure AWS credentials via OIDC
        if: inputs.environment == 'production'
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: us-east-1
          # OIDC — no long-lived secrets needed

      - name: Deploy to ${{ inputs.environment }}
        run: |
          echo "Deploying ${{ inputs.app-name }} to ${{ inputs.environment }}"
          # Your deploy commands here
        env:
          CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          ENVIRONMENT: ${{ inputs.environment }}
# In any repo that needs deployment:
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    uses: your-org/shared-workflows/.github/workflows/deploy-reusable.yml@main
    with:
      environment: staging
      app-name: my-app
    secrets:
      CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

  deploy-production:
    needs: deploy-staging
    uses: your-org/shared-workflows/.github/workflows/deploy-reusable.yml@main
    with:
      environment: production
      app-name: my-app
    secrets: inherit  # Pass all secrets from calling workflow

Environment Protection Rules

Set up staging and production environments.
Production requires two approvals from the platform team.
# .github/workflows/release.yml
name: Release

on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: dist/

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment: staging    # Requires approval from 'staging-approvers' team (configured in UI)
    
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: dist/
      
      - name: Deploy to staging
        run: ./scripts/deploy.sh staging

  integration-tests:
    needs: deploy-staging
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run test:integration
        env:
          APP_URL: https://staging.myapp.com

  deploy-production:
    needs: integration-tests
    runs-on: ubuntu-latest
    environment: production  # Requires 2 approvals from 'platform-team' (configured in UI)
    
    steps:
      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: dist/
      
      - name: Deploy to production
        run: ./scripts/deploy.sh production

Concurrency and Cost Optimization

We're spending too much on GitHub Actions.
Concurrent PR pushes create multiple runs of the same workflow.
Only the latest commit's run should execute.
on:
  pull_request:

# Cancel previous run when new commit pushed to same PR
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    # Use larger runners only for long tests
    runs-on: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'ubuntu-latest-large' || 'ubuntu-latest' }}
    
    steps:
      # Cache dependencies between runs
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'        # Cache npm install

      # Cache build artifacts
      - name: Cache build
        uses: actions/cache@v4
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}-${{ hashFiles('src/**/*.ts') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}-
            ${{ runner.os }}-nextjs-

Workflow Dispatch for Manual Operations

Add a workflow that engineers can manually trigger to run database migrations
in production, with a dry-run option.
# .github/workflows/db-migrate.yml
name: Database Migration

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: choice
        options: ['staging', 'production']
        default: staging
      dry-run:
        description: 'Dry run (show what would change, do not execute)'
        required: true
        type: boolean
        default: true
      confirm-production:
        description: 'Type "yes-i-am-sure" to confirm production migration'
        required: false
        type: string

jobs:
  migrate:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}

    steps:
      - uses: actions/checkout@v4

      - name: Validate production confirmation
        if: inputs.environment == 'production' && !inputs.dry-run
        run: |
          if [ "${{ inputs.confirm-production }}" != "yes-i-am-sure" ]; then
            echo "Production migration requires typing 'yes-i-am-sure' in the confirmation field"
            exit 1
          fi

      - name: Run migration
        run: |
          if [ "${{ inputs.dry-run }}" == "true" ]; then
            npx knex migrate:status --env ${{ inputs.environment }}
          else
            npx knex migrate:latest --env ${{ inputs.environment }}
          fi
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}

      - name: Notify on completion
        if: always()
        run: |
          STATUS="${{ job.status }}"
          DRY_RUN="${{ inputs.dry-run }}"
          ENV="${{ inputs.environment }}"
          echo "Migration job status: $STATUS (dry_run=$DRY_RUN, env=$ENV)"
          # Send Slack notification here

For the CI/CD pipeline patterns including test stages and deployment gates, see the CI/CD guide. For infrastructure provisioning that integrates with these workflows, see the Terraform guide. The Claude Skills 360 bundle includes CI/CD automation skill sets for workflow generation, matrix configuration, and cost optimization. Start with the free tier to try workflow generation for your pipeline.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free