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/cachefor node_modules, pip, Docker layers (BuildKit GHA cache) - Use
ubuntu-latestunless 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-minutesto 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't | Do Instead |
|---|---|
| Store cloud credentials as long-lived secrets | Use OIDC federation |
Use actions/checkout@main | Pin by SHA |
Run everything on macos-latest | Use ubuntu-latest unless macOS-specific |
Skip timeout-minutes | Always set a timeout |
| Hardcode versions in steps | Use matrix or variables |
| Deploy from PRs | Deploy only from main after merge |