A slow CI pipeline is a slow team. When builds take 20 minutes, developers context-switch, stack PRs, and stop running CI before pushing. Fast CI is a force multiplier.
Pipeline Architecture
Structure your pipeline in stages with increasing cost and scope:
Stage 1: Lint + Type Check (30s)
↓
Stage 2: Unit Tests (1-2 min)
↓
Stage 3: Build (1-2 min)
↓
Stage 4: Integration Tests (2-3 min)
↓
Stage 5: E2E Tests (3-5 min)
↓
Stage 6: Deploy (1-2 min)
Fail fast. If linting catches a problem in 30 seconds, don’t wait for a 5-minute E2E suite to tell you.
Parallelization
Run independent jobs concurrently:
# GitHub Actions example
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint && npm run typecheck
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
Lint, test, and other independent checks run in parallel. Build only runs if both pass.
Caching
Cache dependencies aggressively:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
This turns a 60-second npm ci into a 5-second cache restore for most runs.
Also cache:
- Build outputs (Turborepo remote cache)
- Docker layers
- Test databases (restore from a snapshot instead of running migrations)
Only Run What Changed
In a monorepo, don’t run every test for every change:
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
api:
- 'packages/api/**'
web:
- 'packages/web/**'
- if: steps.changes.outputs.api == 'true'
run: npm test --workspace=packages/api
Flaky Test Management
Flaky tests erode trust in CI. Address them aggressively:
- Quarantine flaky tests. Move them to a separate suite that doesn’t block PRs.
- Track flake rate. A test that fails 5% of the time will block 1 in 20 PRs.
- Set a time budget. If a test takes over 30 seconds, it’s probably doing too much.
- Avoid sleep-based waits. Poll for conditions instead.
Deploy Pipeline
Keep deployments simple and reversible:
- Build once, deploy many. The same artifact goes to staging and production.
- Canary deployments. Route a small percentage of traffic to the new version first.
- Automated rollback. If error rates spike after deploy, roll back automatically.
- Deploy frequently. Daily deploys are less risky than weekly deploys.
A good CI pipeline is invisible. Developers push code, and minutes later it’s live — or they know exactly why it isn’t.