GitHub Actions CI/CD
Core Objectives
- Security: Minimal permissions, secrets handling, OIDC
- Performance: Caching, matrix builds, parallelization
- Maintainability: Reusable workflows, composite actions
- Reliability: Concurrency control, retry logic
Essential Checklist
Minimal Permissions
name: CI
on:
push:
branches: [main]
pull_request:
# Default: no permissions
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
# Job-level permissions
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
Caching
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Or explicit caching
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Matrix Builds
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
Concurrency Control
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Secrets Handling
env:
# Reference secrets in env
API_KEY: ${{ secrets.API_KEY }}
steps:
- name: Use secret safely
run: |
# Mask in logs
echo "::add-mask::${{ secrets.API_KEY }}"
# Use in command
curl -H "Authorization: Bearer $API_KEY" https://api.acme.com
Reusable Workflows
# .github/workflows/reusable-build.yml
name: Reusable Build
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: '20'
secrets:
NPM_TOKEN:
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run build
# Calling workflow
jobs:
build:
uses: ./.github/workflows/reusable-build.yml
with:
node-version: '20'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Job Outputs
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.value }}
steps:
- id: version
run: echo "value=$(cat VERSION)" >> $GITHUB_OUTPUT
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploying ${{ needs.build.outputs.version }}"
Detailed References