github-actions
GitHub Actions CI/CD Best Practices
Build robust, secure, and efficient CI/CD pipelines using GitHub Actions.
Quick Reference
| Topic | Reference |
|---|---|
| Security | Secrets, OIDC, least privilege, SAST, SCA, secret scanning |
| Optimization | Caching, matrix strategies, self-hosted runners, artifacts |
| Testing | Unit, integration, E2E, performance, test reporting |
| Deployment | Staging, production, blue/green, canary, rollback |
| Review Checklist | Comprehensive workflow review checklist |
| Troubleshooting | Common issues and fixes |
Workflow Structure
Naming: Use descriptive file names (build-and-test.yml, deploy-prod.yml).
Triggers (on): push, pull_request, workflow_dispatch (manual), schedule (cron), repository_dispatch (external), workflow_call (reusable).
Concurrency: Prevent simultaneous runs for specific branches/groups:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Permissions: Define at workflow level, override at job level. Default to least privilege:
permissions:
contents: read
Reusable workflows: Use workflow_call to abstract common CI/CD patterns across projects.
Jobs
- Jobs represent distinct pipeline phases: build, test, lint, deploy
- Use
needsfor dependencies between jobs - Use
outputsto pass data between jobs - Use
iffor conditional execution
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact_path: ${{ steps.package.outputs.path }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci && npm run build
- id: package
run: |
zip -r dist.zip dist
echo "path=dist.zip" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v3
with:
name: app-build
path: dist.zip
deploy-staging:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v3
with:
name: app-build
- run: echo "Deploying ${{ needs.build.outputs.artifact_path }}"
Steps and Actions
- Version pinning: Pin actions to commit SHA or major version tag (
@v4). Never usemainorlatest. - Descriptive names: Every step needs a clear
namefor log readability. - Multi-line scripts: Use
|for complex commands, combine with&&for efficiency. - Audit actions: Prefer actions from
actions/org. Use Dependabot for version updates.
Key Patterns
Secret management
env:
API_KEY: ${{ secrets.API_KEY }}
Never hardcode secrets. Use environment-specific secrets for deployment.
OIDC cloud authentication
Use short-lived credentials instead of stored access keys:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::role/deploy
aws-region: us-east-1
Caching dependencies
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
Matrix testing
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18.x, 20.x]
Fast checkout
- uses: actions/checkout@v4
with:
fetch-depth: 1 # Shallow clone for speed
Deployment Types
| Strategy | When to use |
|---|---|
| Rolling | Default, stateless apps. Configure maxSurge/maxUnavailable |
| Blue/Green | Zero-downtime, instant rollback. Two identical environments |
| Canary | Controlled blast radius. Route 5-10% traffic to new version |
| Dark Launch | Decouple deploy from release. Feature flags control exposure |
Troubleshooting
Common issues: workflow not triggering (check on triggers, if conditions, concurrency), permission errors (check permissions block, secret access), cache misses (validate keys with hashFiles), long runtimes (profile steps, add caching, parallelize with matrix).
See Troubleshooting for detailed fixes.