Fast, reliable CI/CD pipelines are essential for developer productivity. This guide covers optimization techniques for GitHub Actions and general pipeline best practices.
Pipeline Architecture#
Optimized Workflow Structure#
1# .github/workflows/ci.yml
2name: CI/CD Pipeline
3
4on:
5 push:
6 branches: [main]
7 pull_request:
8 branches: [main]
9
10concurrency:
11 group: ${{ github.workflow }}-${{ github.ref }}
12 cancel-in-progress: true
13
14jobs:
15 # Fast checks first
16 lint:
17 runs-on: ubuntu-latest
18 steps:
19 - uses: actions/checkout@v4
20 - uses: actions/setup-node@v4
21 with:
22 node-version: 20
23 cache: 'pnpm'
24 - run: pnpm install --frozen-lockfile
25 - run: pnpm lint
26
27 typecheck:
28 runs-on: ubuntu-latest
29 steps:
30 - uses: actions/checkout@v4
31 - uses: actions/setup-node@v4
32 with:
33 node-version: 20
34 cache: 'pnpm'
35 - run: pnpm install --frozen-lockfile
36 - run: pnpm typecheck
37
38 # Tests run in parallel
39 test:
40 runs-on: ubuntu-latest
41 strategy:
42 matrix:
43 shard: [1, 2, 3, 4]
44 steps:
45 - uses: actions/checkout@v4
46 - uses: actions/setup-node@v4
47 with:
48 node-version: 20
49 cache: 'pnpm'
50 - run: pnpm install --frozen-lockfile
51 - run: pnpm test --shard=${{ matrix.shard }}/4
52
53 # Build only after checks pass
54 build:
55 needs: [lint, typecheck, test]
56 runs-on: ubuntu-latest
57 steps:
58 - uses: actions/checkout@v4
59 - uses: actions/setup-node@v4
60 with:
61 node-version: 20
62 cache: 'pnpm'
63 - run: pnpm install --frozen-lockfile
64 - run: pnpm build
65
66 - uses: actions/upload-artifact@v4
67 with:
68 name: build
69 path: dist/
70
71 deploy:
72 if: github.ref == 'refs/heads/main'
73 needs: [build]
74 runs-on: ubuntu-latest
75 environment: production
76 steps:
77 - uses: actions/download-artifact@v4
78 with:
79 name: build
80 path: dist/
81 - run: ./deploy.shCaching Strategies#
Dependency Caching#
1# Node.js with pnpm
2- uses: pnpm/action-setup@v3
3 with:
4 version: 9
5
6- uses: actions/setup-node@v4
7 with:
8 node-version: 20
9 cache: 'pnpm'
10
11# Custom cache for build artifacts
12- uses: actions/cache@v4
13 with:
14 path: |
15 .next/cache
16 node_modules/.cache
17 key: build-cache-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('src/**') }}
18 restore-keys: |
19 build-cache-${{ hashFiles('pnpm-lock.yaml') }}-
20 build-cache-Docker Layer Caching#
1build-docker:
2 runs-on: ubuntu-latest
3 steps:
4 - uses: actions/checkout@v4
5
6 - uses: docker/setup-buildx-action@v3
7
8 - uses: docker/build-push-action@v5
9 with:
10 context: .
11 push: true
12 tags: myapp:${{ github.sha }}
13 cache-from: type=gha
14 cache-to: type=gha,mode=maxTurbo Remote Caching#
1- name: Setup Turbo cache
2 uses: actions/cache@v4
3 with:
4 path: .turbo
5 key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
6 restore-keys: |
7 turbo-${{ github.job }}-${{ github.ref_name }}-
8 turbo-${{ github.job }}-
9
10- run: pnpm turbo build --cache-dir=.turboTest Optimization#
Parallel Test Execution#
1test:
2 runs-on: ubuntu-latest
3 strategy:
4 fail-fast: false
5 matrix:
6 shard: [1, 2, 3, 4]
7 steps:
8 - uses: actions/checkout@v4
9 - uses: actions/setup-node@v4
10 with:
11 node-version: 20
12 cache: 'pnpm'
13
14 - run: pnpm install --frozen-lockfile
15 - run: pnpm test --shard=${{ matrix.shard }}/4
16 env:
17 CI: true
18
19# Merge coverage reports
20coverage:
21 needs: test
22 runs-on: ubuntu-latest
23 steps:
24 - uses: actions/download-artifact@v4
25 with:
26 pattern: coverage-*
27 merge-multiple: true
28
29 - run: npx nyc merge coverage/ merged-coverage.json
30 - run: npx nyc report --reporter=text-summarySelective Testing#
1test:
2 runs-on: ubuntu-latest
3 steps:
4 - uses: actions/checkout@v4
5 with:
6 fetch-depth: 0
7
8 - name: Get changed files
9 id: changed
10 run: |
11 echo "files=$(git diff --name-only origin/main...HEAD | xargs)" >> $GITHUB_OUTPUT
12
13 - name: Run affected tests
14 run: |
15 pnpm turbo test --filter=...[origin/main]Deployment Strategies#
Blue-Green Deployment#
1deploy:
2 runs-on: ubuntu-latest
3 environment: production
4 steps:
5 - name: Deploy to blue environment
6 run: |
7 kubectl apply -f k8s/blue-deployment.yaml
8 kubectl wait --for=condition=available deployment/app-blue
9
10 - name: Run smoke tests
11 run: |
12 ./scripts/smoke-test.sh https://blue.example.com
13
14 - name: Switch traffic to blue
15 run: |
16 kubectl patch service app -p '{"spec":{"selector":{"version":"blue"}}}'
17
18 - name: Cleanup green
19 run: |
20 kubectl delete deployment app-green --ignore-not-foundCanary Deployment#
1deploy-canary:
2 runs-on: ubuntu-latest
3 steps:
4 - name: Deploy canary (10%)
5 run: |
6 kubectl apply -f k8s/canary-deployment.yaml
7 kubectl scale deployment app-canary --replicas=1
8 kubectl scale deployment app-stable --replicas=9
9
10 - name: Monitor for 10 minutes
11 run: |
12 ./scripts/monitor-canary.sh 10m
13
14 - name: Check error rate
15 id: check
16 run: |
17 error_rate=$(./scripts/get-error-rate.sh canary)
18 if (( $(echo "$error_rate > 0.01" | bc -l) )); then
19 echo "result=rollback" >> $GITHUB_OUTPUT
20 else
21 echo "result=promote" >> $GITHUB_OUTPUT
22 fi
23
24 - name: Rollback or Promote
25 run: |
26 if [ "${{ steps.check.outputs.result }}" == "rollback" ]; then
27 kubectl delete deployment app-canary
28 else
29 kubectl scale deployment app-canary --replicas=10
30 kubectl delete deployment app-stable
31 kubectl patch deployment app-canary --patch '{"metadata":{"name":"app-stable"}}'
32 fiSecurity in Pipelines#
Secret Management#
1deploy:
2 runs-on: ubuntu-latest
3 permissions:
4 id-token: write
5 contents: read
6 steps:
7 # Use OIDC for cloud authentication
8 - uses: aws-actions/configure-aws-credentials@v4
9 with:
10 role-to-assume: arn:aws:iam::123456789:role/github-actions
11 aws-region: us-east-1
12
13 # Never echo secrets
14 - run: |
15 ./deploy.sh
16 env:
17 API_KEY: ${{ secrets.API_KEY }}Dependency Scanning#
1security:
2 runs-on: ubuntu-latest
3 steps:
4 - uses: actions/checkout@v4
5
6 - name: Run Trivy vulnerability scanner
7 uses: aquasecurity/trivy-action@master
8 with:
9 scan-type: 'fs'
10 severity: 'CRITICAL,HIGH'
11 exit-code: '1'
12
13 - name: Check for secrets
14 uses: gitleaks/gitleaks-action@v2Pipeline Metrics#
Tracking Build Times#
1- name: Record build metrics
2 if: always()
3 run: |
4 curl -X POST https://metrics.example.com/api/builds \
5 -H "Content-Type: application/json" \
6 -d '{
7 "repository": "${{ github.repository }}",
8 "workflow": "${{ github.workflow }}",
9 "job": "${{ github.job }}",
10 "status": "${{ job.status }}",
11 "duration": "${{ github.run_duration }}",
12 "sha": "${{ github.sha }}"
13 }'Optimization Checklist#
- Use concurrency controls: Cancel redundant runs
- Parallelize where possible: Tests, linting, builds
- Cache aggressively: Dependencies, build artifacts
- Fail fast: Run quick checks first
- Use matrix builds: Test across configurations
- Minimize checkout: Use sparse checkout when possible
- Choose right runners: Use larger runners for builds
- Skip unnecessary steps: Use path filters
Common Issues#
Flaky Tests#
1test:
2 steps:
3 - run: pnpm test
4 # Retry on failure
5 continue-on-error: true
6 id: test1
7
8 - run: pnpm test
9 if: steps.test1.outcome == 'failure'Long Install Times#
1# Use frozen lockfile
2- run: pnpm install --frozen-lockfile
3
4# Or use bun for faster installs
5- uses: oven-sh/setup-bun@v1
6- run: bun installConclusion#
Fast pipelines improve developer experience and deployment frequency. Focus on parallelization, caching, and fail-fast strategies. Measure your pipeline performance and continuously optimize bottlenecks.