Pipelines That Tell You What's Wrong
A CI/CD pipeline that takes 40 minutes to run isn't a pipeline β it's a deployment tax. Developers stop running it locally, stop reading the output, and merge with crossed fingers. Good pipeline design is about fast feedback: fail early, fail clearly, and only block on things that actually matter.
- Your pipeline runs everything sequentially when most jobs could run in parallel
- A flaky E2E test blocks deployments randomly, so the team ignores it
- You have no idea if your pipeline is getting faster or slower over time
- Your βquality gateβ is βit compiles and tests passβ β nothing more
DORA metrics give you a quantified baseline. Pipeline topology and quality gate design give you a path to improve them.
DORA Metrics: The Baseline
DORA (DevOps Research and Assessment) defined four metrics that correlate with engineering performance. They're not vanity metrics β they're leading indicators of reliability and throughput.
quadrantChart
title DORA Performance Tiers
x-axis Slow Deployment Frequency --> High Deployment Frequency
y-axis High Change Failure Rate --> Low Change Failure Rate
quadrant-1 Elite
quadrant-2 High
quadrant-3 Low
quadrant-4 Medium
Elite: [0.9, 0.9]
High: [0.65, 0.75]
Medium: [0.4, 0.45]
Low: [0.2, 0.25]
Elite performers (from the 2023 State of DevOps Report):
- Deployment Frequency: Multiple deploys per day
- Lead Time for Changes: Less than 1 hour from commit to production
- Change Failure Rate: 0β5% of deploys cause a production failure
- Time to Restore Service: Less than 1 hour when something breaks
Pipeline Topology
# Principles:
# 1. Fail fast β cheapest checks first
# 2. Parallelise where possible β lint and test run independently
# 3. Cache aggressively β don't reinstall deps on every run
# 4. Artifact handoff β build once, deploy that exact artifact
# Optimal job graph for a Node.js service:
#
# [pr-check] ββ¬β [lint + typecheck] (2 min)
# ββ [unit tests + coverage] (3 min)
# ββ [build] (4 min)
#
# On main merge:
# [integration tests] (5 min)
# ββ [docker build + push] (3 min)
# ββ [deploy staging] (2 min)
# ββ [smoke tests] (1 min)
# ββ [deploy production] (2 min, manual approval gate)
jobs:
# Stage 1: Fast static checks (no runtime needed)
lint-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'pnpm' }
- run: pnpm install --frozen-lockfile
- run: pnpm lint && pnpm typecheck
# Stage 1 (parallel): Tests with coverage gate
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'pnpm' }
- run: pnpm install --frozen-lockfile
- run: pnpm test --coverage
- name: Enforce coverage threshold
run: node -e "
const c = require('./coverage/coverage-summary.json').total;
const fail = (n, t) => { if (c[n].pct < t) { console.error(n + ': ' + c[n].pct + '% < ' + t + '%'); process.exit(1); } };
fail('lines', 80); fail('branches', 75);"
# Stage 2: Build (only after stage 1 passes)
build:
needs: [lint-typecheck, test]
runs-on: ubuntu-latest
outputs:
image: ${{ steps.push.outputs.imageid }}
steps:
- uses: actions/checkout@v4
- uses: docker/build-push-action@v5
id: push
with:
push: true
tags: ghcr.io/myorg/api:${{ github.sha }}Quality Gates
A quality gate is a hard stop: the pipeline fails if a metric falls below a threshold. Soft warnings that don't block deploys are ignored within a week.
# Example quality gates in CI:
# 1. Test coverage
- run: pnpm test --coverage --coverageThreshold='{"global":{"lines":80,"branches":75}}'
# 2. Bundle size (prevent accidental bloat)
- name: Check bundle size
uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# .size-limit.js configures thresholds β fails if exceeded
# 3. Dependency audit (no high/critical CVEs)
- run: pnpm audit --audit-level=high
# 4. Performance budget (Lighthouse CI)
- uses: treosh/lighthouse-ci-action@v10
with:
budgetPath: ./budget.json
uploadArtifacts: trueFlaky Test Management
Flaky tests are the silent killers of pipeline trust. Once developers learn a failure might be flaky, they start assuming everything is flaky β and stop taking failures seriously.
# Quarantine pattern: move flaky tests to a separate job
# that doesn't block the merge, but still tracks failure rate
jobs:
test:
steps:
- run: pnpm test # Only stable tests β blocks merge
test-flaky:
continue-on-error: true # Doesn't fail the workflow
steps:
- run: pnpm test:flaky # Quarantined flaky tests
# But: alert/notify if failure rate > threshold
- if: failure()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} -d '{"text": "Flaky test failure: ${{ github.run_id }}"}'Measuring Your Pipeline
# Record job timings and publish to your observability stack
# GitHub Actions exposes duration via the API, or use this pattern:
- name: Record pipeline duration
if: always() # Run even if previous steps failed
run: |
DURATION=$(($(date +%s) - ${{ steps.start_time.outputs.time }}))
# Push metric to your TSDB (Datadog, InfluxDB, etc.)
curl -X POST "https://api.datadoghq.com/api/v1/series" -H "DD-API-KEY: ${{ secrets.DD_API_KEY }}" -d '{
"series": [{
"metric": "ci.pipeline.duration_seconds",
"points": [['$(date +%s)', '$DURATION']],
"tags": ["job:${{ github.job }}", "repo:${{ github.repository }}"]
}]
}'Pitfalls
Caching test artifacts between runs
Cache node_modulesby lockfile hash. Don't cache test output or build artifacts between runs of the same job β they should be reproducible from source. The exception: test coverage history (for trending) should persist to an external store.
Coverage thresholds without baseline
Enforcing 80% coverage on a codebase that has 40% coverage today will make the pipeline red immediately. Start below your current level and ratchet up over time using a coverage database (e.g., Codecov ratchet mode).
E2E tests in the hot path
E2E tests belong after staging deploy β not before. They're slow, environment-dependent, and often flaky. Running them before merge adds 15 minutes to every PR cycle. Test the integration contract in unit/integration tests; validate end-to-end behaviour on the staging environment.

