GitHub Actions CI/CD Patterns That Actually Work
Practical patterns for GitHub Actions workflows — caching, secret management, conditional deployments, and the mistakes that slow down pipelines.
GitHub Actions is ubiquitous now — it’s the default CI/CD choice for most new projects and an increasing number of migrations away from Jenkins and CircleCI. After writing a lot of workflows, we’ve accumulated a set of patterns that make pipelines reliable and fast.
Cache Your Dependencies
The single highest-impact optimization: cache node_modules between runs. Without caching, every pipeline run reinstalls your entire dependency tree.
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
The cache: 'npm' option caches based on package-lock.json hash. If the lockfile doesn’t change, the cache hits. On a project with 500+ packages, this is the difference between a 2-minute pipeline and a 45-second one.
Separate Jobs for Separate Concerns
Don’t put everything in one job. A monolithic workflow is hard to debug and can’t parallelize.
jobs:
lint:
runs-on: ubuntu-latest
steps: [...]
test:
runs-on: ubuntu-latest
steps: [...]
build:
needs: [lint, test]
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps: [...]
lint and test run in parallel. build waits for both to pass. deploy only runs on main. Each job’s logs are isolated, failures are obvious, and the critical path is clear.
Conditional Deployments
The if conditional on jobs is powerful and underused:
# Deploy only on main branch pushes
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# Skip deploy on draft PRs
if: github.event.pull_request.draft == false
# Only run expensive job when relevant files change
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
src:
- 'src/**'
- 'package.json'
Secret Rotation Strategy
Treat pipeline secrets with the same discipline as production secrets. Rotation policy:
- AWS credentials: rotate every 90 days, use IAM roles with minimal permissions
- Third-party API tokens: rotate on personnel changes
- Never put secrets in workflow files — always use
${{ secrets.SECRET_NAME }}
For complex deployments with many secrets, consider AWS Secrets Manager and retrieve them within the workflow rather than storing them all in GitHub.
The Reusable Workflow Pattern
For organizations with multiple repos that share deployment patterns, reusable workflows avoid copy-paste drift:
# .github/workflows/deploy-s3.yml (in a shared repo)
on:
workflow_call:
inputs:
bucket:
required: true
type: string
secrets:
AWS_ACCESS_KEY_ID:
required: true
Other repos call it with uses: org/shared-workflows/.github/workflows/deploy-s3.yml@main. One update propagates everywhere.
Pipeline Observability
Add a final step that posts deployment status to Slack or records a deployment marker. Silent pipelines hide problems. The team should know within minutes if a deploy failed and exactly which step broke.