Back to Blog
CI/CDDevOpsGitHub ActionsDeployment

CI/CD Pipeline Optimization: Faster, Safer Deployments

Optimize your CI/CD pipelines for speed and reliability. Learn caching, parallelization, and deployment strategies.

B
Bootspring Team
Engineering
February 26, 2026
5 min read

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.sh

Caching 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=max

Turbo 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=.turbo

Test 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-summary

Selective 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-found

Canary 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 fi

Security 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@v2

Pipeline 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#

  1. Use concurrency controls: Cancel redundant runs
  2. Parallelize where possible: Tests, linting, builds
  3. Cache aggressively: Dependencies, build artifacts
  4. Fail fast: Run quick checks first
  5. Use matrix builds: Test across configurations
  6. Minimize checkout: Use sparse checkout when possible
  7. Choose right runners: Use larger runners for builds
  8. 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 install

Conclusion#

Fast pipelines improve developer experience and deployment frequency. Focus on parallelization, caching, and fail-fast strategies. Measure your pipeline performance and continuously optimize bottlenecks.

Share this article

Help spread the word about Bootspring