GitHub Actions has evolved into a sophisticated automation platform. Beyond basic CI/CD, it offers powerful features for building complex workflows, creating reusable components, and implementing enterprise-grade automation strategies.
Composite Actions
Creating reusable composite actions encapsulates complex logic into shareable units.
# .github/actions/setup-node-env/action.yml
name: 'Setup Node Environment'
description: 'Setup Node.js with caching and dependencies'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '20'
package-manager:
description: 'Package manager to use'
required: false
default: 'npm'
runs:
using: 'composite'
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: ${{ inputs.package-manager }}
- name: Install dependencies
shell: bash
run: |
if [ "${{ inputs.package-manager }}" = "npm" ]; then
npm ci
elif [ "${{ inputs.package-manager }}" = "yarn" ]; then
yarn install --frozen-lockfile
elif [ "${{ inputs.package-manager }}" = "pnpm" ]; then
pnpm install --frozen-lockfile
fi# Using the composite action
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-env
with:
node-version: '20'
package-manager: 'pnpm'Complex Matrix Strategies
Dynamic Matrices
name: Dynamic Matrix Builds
on:
push:
branches: [main]
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
MATRIX='{"include":['
MATRIX+='{"os":"ubuntu-latest","node":"18","arch":"x64"},'
MATRIX+='{"os":"ubuntu-latest","node":"20","arch":"x64"},'
MATRIX+='{"os":"windows-latest","node":"20","arch":"x64"},'
MATRIX+='{"os":"macos-latest","node":"20","arch":"arm64"}'
MATRIX+=']}'
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
test:
needs: setup
runs-on: ${{ matrix.os }}
strategy:
matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
architecture: ${{ matrix.arch }}
- run: npm testExclusion and Inclusion Patterns
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [16, 18, 20, 21]
ruby: [2.7, 3.0, 3.1]
exclude:
- os: windows-latest
node: 16
- os: macos-latest
node: 21
- os: windows-latest
ruby: 2.7
include:
- os: ubuntu-latest
node: 20
coverage: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
- if: matrix.coverage
run: npm run test:coverageReusable Workflows
Creating Reusable Workflows
# .github/workflows/reusable-ci.yml
name: Reusable CI Workflow
on:
workflow_call:
inputs:
node-version:
required: true
type: string
run-tests:
required: false
type: boolean
default: true
secrets:
npm-token:
required: true
outputs:
test-result:
description: 'Test results'
value: ${{ jobs.test.outputs.result }}
jobs:
lint:
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 lint
test:
if: inputs.run-tests
runs-on: ubuntu-latest
outputs:
result: ${{ steps.test.outputs.result }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- id: test
run: |
npm test
echo "result=success" >> $GITHUB_OUTPUT
- run: npm run test:coverageCalling Reusable Workflows
name: CI Pipeline
on:
push:
branches: [main, develop]
jobs:
call-ci:
uses: ./.github/workflows/reusable-ci.yml
with:
node-version: '20'
run-tests: true
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}Advanced Caching
Multi-Level Caching Strategy
name: Advanced Caching
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache node modules
uses: actions/cache@v3
id: npm-cache
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache build outputs
uses: actions/cache@v3
with:
path: |
.next
dist
build
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cacheAdvanced Deployment Strategies
Canary Deployments
name: Canary Deployment
on:
push:
branches: [main]
jobs:
deploy-canary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy canary (10% traffic)
run: |
kubectl apply -f k8s/canary-deployment.yaml
kubectl patch service myapp -p '{"spec":{"selector":{"version":"canary"}}}'
# Update service to route 10% to canary
- name: Wait for stability
run: sleep 300
- name: Monitor metrics
run: |
ERROR_RATE=$(curl -s https://api.monitoring.com/error-rate)
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
echo "Error rate too high, rolling back"
kubectl rollout undo deployment/myapp
exit 1
fi
deploy-full:
needs: deploy-canary
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: |
kubectl apply -f k8s/production-deployment.yaml
kubectl rollout status deployment/myappA/B Testing Deployment
jobs:
deploy-variant-a:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy variant A
run: |
kubectl apply -f k8s/variant-a.yaml
kubectl patch service myapp \
-p '{"spec":{"selector":{"variant":"a"}}}'
deploy-variant-b:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy variant B
run: |
kubectl apply -f k8s/variant-b.yaml
kubectl patch service myapp \
-p '{"spec":{"selector":{"variant":"b"}}}'
analyze:
needs: [deploy-variant-a, deploy-variant-b]
runs-on: ubuntu-latest
steps:
- name: Run A/B test analysis
run: |
# Collect metrics and determine winner
npm run analyze-ab-testEnvironment-Specific Workflows
Multi-Environment Deployments
name: Multi-Environment Deployment
on:
push:
branches: [main]
environments:
staging:
url: https://staging.example.com
production:
url: https://example.com
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment:
name: staging
deployment_url: ${{ steps.deploy.outputs.url }}
outputs:
version: ${{ steps.deploy.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: deploy
run: |
VERSION=$(npm version patch)
npm run build
npm run deploy:staging
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
test-staging:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- name: Run integration tests
run: |
npm run test:integration -- --env=staging
promote-production:
needs: [deploy-staging, test-staging]
runs-on: ubuntu-latest
environment:
name: production
steps:
- uses: actions/checkout@v4
- run: |
VERSION=${{ needs.deploy-staging.outputs.version }}
npm run deploy:production -- --version=$VERSIONComplex Workflow Orchestration
Fan-Out/Fan-In Pattern
name: Fan-Out/Fan-In Pattern
on:
push:
branches: [main]
jobs:
setup:
runs-on: ubuntu-latest
outputs:
versions: ${{ steps.versions.outputs.versions }}
steps:
- uses: actions/checkout@v4
- id: versions
run: |
VERSIONS=$(node -e "console.log(require('./package.json').versions.join(','))")
echo "versions=$VERSIONS" >> $GITHUB_OUTPUT
build-variants:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix:
variant: ${{ fromJson(needs.setup.outputs.versions) }}
steps:
- uses: actions/checkout@v4
- run: npm run build -- --variant=${{ matrix.variant }}
- uses: actions/upload-artifact@v3
with:
name: build-${{ matrix.variant }}
path: dist/
combine:
needs: build-variants
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
with:
path: artifacts/
- run: |
# Combine all artifacts
npm run combine -- --source=artifacts/ --output=final/
- uses: actions/upload-artifact@v3
with:
name: final-build
path: final/Advanced Testing Patterns
Flaky Test Detection
jobs:
test-with-retry:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests with retry
uses: nick-fields/retry-action@v2
with:
timeout_minutes: 10
max_attempts: 3
retry_on: error
command: npm test
- name: Report flaky tests
if: failure()
run: |
# Log flaky test detection
echo "::warning::Tests failed after 3 attempts"Parallel Test Execution
jobs:
setup:
runs-on: ubuntu-latest
outputs:
files: ${{ steps.split.outputs.files }}
steps:
- uses: actions/checkout@v4
- id: split
run: |
FILES=$(find . -name "*.test.ts" -printf '%p,' | sed 's/,$//')
echo "files=$FILES" >> $GITHUB_OUTPUT
test:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
Security and Compliance
Security Scanning Pipeline
name: Security Pipeline
on:
push:
branches: [main]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --audit-level=high
continue-on-error: true
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v3
with:
fail-on-severity: highPerformance Optimization
Parallel Job Optimization
jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run test:unit
test-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run test:integration
test-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run test:e2e
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run type-check
build:
runs-on: ubuntu-latest
needs: [test-unit, test-integration, test-e2e, lint, type-check]
steps:
- uses: actions/checkout@v4
- run: npm run buildConclusion
Advanced GitHub Actions patterns enable sophisticated automation strategies. By leveraging composite actions, reusable workflows, complex matrices, and advanced deployment patterns, you can build enterprise-grade CI/CD pipelines that scale with your team's needs.
The key is to iterate gradually: start simple, measure performance, and optimize based on real data.