How we cut build times in half without changing the stack
For months our continuous integration pipeline had been the quiet tax on every pull request. Engineers waited, context-switched, and sometimes simply stopped opening small fixes because a green build felt too expensive. We did not want a rewrite. We wanted the same languages, the same package managers, and the same deploy path—only faster.
What followed was less a single clever trick than a disciplined pass over how work actually moved through the system. Caching was incomplete. Test shards were uneven. A handful of legacy jobs still ran serially for no reason anyone could defend. Each fix was modest; together they halved wall-clock time.
Start by measuring, not guessing
Before we touched configuration we instrumented the pipeline end to end. We logged stage durations, cache hit rates, and which suites dominated CPU. The data was unkind in useful ways: our “optimized” Docker layer cache was barely used, and one flaky integration suite alone accounted for nearly a fifth of median runtime.
Speed is a product decision disguised as infrastructure. Treat every wasted minute as a bug you would otherwise ship.
With numbers on the board, prioritization became obvious. We fixed the cache keys so dependency layers invalidated only when lockfiles changed. We rebalanced shards by historical duration rather than by file count. We deleted two jobs that duplicated coverage already enforced elsewhere. None of that required a new stack—only attention.
Six weeks later median pull-request builds finished in half the time. The stack was the same. The culture around waiting for green was not.