← Back to docs

GitHub Actions CI/CD Patterns for DevOps

Reusable patterns, security hardening, and cost-saving techniques for production GitHub Actions workflows.


Overview

A collection of battle-tested patterns for GitHub Actions workflows, covering reusable workflows, security hardening, caching, matrix builds, and cost optimization.

Pattern 1: Reusable Workflow Library

Define workflows once, call from many repos:

# .github/workflows/build-push.yml (in your shared repo)
name: Build and Push
on:
  workflow_call:
    inputs:
      image-name:
        required: true
        type: string
    secrets:
      registry-password:
        required: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.registry-password }}
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository_owner }}/${{ inputs.image-name }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Call it from any repo:

jobs:
  build:
    uses: your-org/shared-workflows/.github/workflows/build-push.yml@main
    with:
      image-name: my-service
    secrets:
      registry-password: ${{ secrets.GITHUB_TOKEN }}

Pattern 2: OIDC for Cloud Authentication (No Stored Secrets)

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789:role/github-actions
      aws-region: us-east-1

Benefits: no long-lived secrets in GitHub, automatic rotation, audit trail in CloudTrail.

Pattern 3: Pin Actions by SHA

# Bad: mutable tag
- uses: actions/checkout@v4

# Good: immutable SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

Use Dependabot or Renovate to keep SHAs updated automatically.

Pattern 4: Matrix Strategy for Multi-Version Testing

strategy:
  fail-fast: false
  matrix:
    node: [18, 20, 22]
    os: [ubuntu-latest, macos-latest]

steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node }}
  - run: npm ci && npm test

Pattern 5: Concurrency Control

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

Prevents concurrent deployments to the same branch/environment.

Pattern 6: Conditional Jobs (Deploy Only on Main)

jobs:
  test:
    runs-on: ubuntu-latest
    steps: [...]

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps: [...]

Pattern 7: Cost Optimization

  • Cache aggressively: actions/cache for node_modules, pip, Docker layers (BuildKit GHA cache)
  • Use ubuntu-latest unless you specifically need macOS/Windows (10x cost difference)
  • Self-hosted runners for high-volume repos (break even at ~2000 min/month)
  • Timeout jobs: always set timeout-minutes to prevent runaway builds
  • Skip CI: put [skip ci] in commit messages for docs-only changes
jobs:
  build:
    timeout-minutes: 15
    if: "!contains(github.event.head_commit.message, '[skip ci]')"

Pattern 8: Security Scanning Pipeline

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

      # Secret scanning
      - uses: gitleaks/gitleaks-action@v2

      # Container scanning
      - uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
          severity: "HIGH,CRITICAL"
          exit-code: "1"

      # IaC scanning
      - uses: aquasecurity/tfsec-action@v1.0.3
        with:
          working_directory: terraform/

Pattern 9: Environment Protection Rules

jobs:
  deploy-prod:
    environment:
      name: production
      url: https://app.example.com
    runs-on: ubuntu-latest
    steps: [...]

Configure in repo Settings → Environments: required reviewers, wait timers, branch restrictions.

Anti-Patterns to Avoid

Don'tDo Instead
Store cloud credentials as long-lived secretsUse OIDC federation
Use actions/checkout@mainPin by SHA
Run everything on macos-latestUse ubuntu-latest unless macOS-specific
Skip timeout-minutesAlways set a timeout
Hardcode versions in stepsUse matrix or variables
Deploy from PRsDeploy only from main after merge

Reactions & comments