Last October, our deployment pipeline hit a wall. Forty-five minutes from merge to production, every single time. Engineers were batching changes into larger PRs to avoid the wait, which meant bigger diffs, longer reviews, and more conflicts. The irony was hard to miss: a CI system designed to ship faster had become the reason we shipped slower. I spent two weeks rewriting our workflow graph, and the results surprised even me.
The Pipeline Problem
Our original config ran twelve jobs sequentially. Lint, type-check, unit tests, integration tests, build Docker images, push to registry, deploy to staging, run smoke tests, approve, deploy to canary, validate metrics, promote to production. Each step waited for the previous one to finish, even when there was zero data dependency between them. The staging deploy was blocked behind a full integration suite that rarely caught issues the unit tests missed.
# Before: sequential, 45 min total
workflows:
build-and-deploy:
jobs:
- lint
- typecheck
- test-unit
- test-integration
- build-images
- deploy-staging
The fix was embarrassingly simple once I mapped the actual dependency graph. Lint and type-check have no reason to block tests. Docker image builds can start as soon as compilation finishes — they don't need test results. We restructured the workflow into three parallel tracks that converge at the staging deploy gate.