← All posts
6 min readThe Cachely team

How to improve your CI times

A slow CI pipeline taxes a team in ways that are hard to measure: context-switching as developers wait for feedback, blocked deployments, and the creeping pressure to skip checks because “the build is slow anyway”. Yet many teams treat CI times as a fixed cost, something to accept rather than optimize. It does not have to be that way. With a systematic diagnostic process and a focus on the most common pitfalls, substantial speedups are within reach.

Start with diagnosis, not assumptions

The first mistake teams make is optimizing the wrong thing. A pipeline is a chain, and only the slowest link matters. Guessing where time goes and rushing to fix it often wastes effort on tasks that account for 5% of the total duration while the real culprit hides elsewhere. Before changing anything, you need to know where the time actually goes.

Begin with dashboards and logs. Most CI platforms (GitHub Actions, Jenkins, GitLab) provide build timings broken down by step. Enable verbose logging on a few recent runs and chart the time per step: checkout, install, lint, test, build. Binary-search the pipeline by disabling steps one at a time to find the bottleneck. Compare CI times against local builds on the same branch - a CI time that is dramatically slower than local often points to resource constraints (CPU, memory, or disk I/O) rather than logic. Check your team's incident history: if the CI was fast six months ago, look at what changed (dependency versions, test count, tool configuration, runner size).

This diagnosis takes an hour but often saves weeks of blind optimization. You will rarely guess the true bottleneck on the first try.

The four most common anti-patterns

Once you know where time goes, you usually find one of these culprits:

  • Bad configuration. A tool is running in serial when it could run in parallel. Dependencies are specified incorrectly so jobs wait on each other unnecessarily. Machine resources (CPU, memory, workers) are under-provisioned for the workload.
  • Outdated tooling versions. A year-old eslint, jest, webpack, or esbuild leaves performance improvements on the table. Tools in this space improve constantly - a version bump often yields 10-30% speedups for free.
  • Broken tool interactions. A Jest config has too many workers, so threads thrash each other. A webpack bundler is optimized for dev instead of production. Node.js is pinned to an old version missing VM improvements.
  • Missing parallelism. Tests run serially instead of in parallel. Build steps that could fan out stay in a single stream. The codebase has many independent modules but they all block on a single dependency resolution step.

Where small tunings yield big wins

The leverage is highest in three areas that teams often overlook:

  • Runner size and CPU/memory. A CI job starved for RAM will thrash disk; a single extra CPU core can cut test duration by 40%. Do not assume the default runner is optimal. Right-size it to your workload. Run a benchmark with doubled resources and see if the build time improves; if it does, the bottleneck is compute, and the return on investment is immediate.
  • Jest and Vitest worker configuration. Jest defaults to workers: (cpuCount / 2). On a 4-core machine that is two workers, and test flakiness often forces teams to lower it further. Try increasing it, or use Vitest with its superior worker scheduling if your project allows. Many teams see 2x speedups here.
  • Parallel task execution in monorepos. If you are using Nx, configure executorThreads and ensure your task graph is correctly scoped so independent tasks do not block each other. A flat task graph where everything runs in series is the enemy; task dependencies should be tight and explicit.

Caching: when it pays off

The biggest CI-time reduction usually comes from caching - skipping work entirely rather than doing it faster. But caching is not free: it adds complexity, takes time to set up, and is only worth the investment when the payoff is clear.

A simple rule: if your CI time exceeds five minutes, caching usually pays for itself. Below that, the overhead of managing a cache often costs more than the speedup. Above five minutes, especially if developers run many builds and CI runs frequently, a hit rate of even 30-40% can cut overall time substantially. The more builds per day (PR feedback loops, CI retries, developer local builds), the stronger the case for investing in caching infrastructure.

Start with local caching (most tools support this out of the box). Then, if your team is mid-to-large and CI times are a bottleneck, a self-hosted or managed remote cache (like Cachely) becomes worthwhile. The jump from local to remote caching typically buys you a 2-5x speedup in average CI time because all developers and CI pipelines share the same cache.

If you are choosing a remote cache setup, compare the options. For the correctness model behind cache hits and misses, start with what makes a good build cache.

A practical checklist

Use this order to attack CI slowness:

  • Measure first. Chart the time per step. Binary-search the bottleneck. Do not guess.
  • Fix configuration. Ensure parallelism is enabled, task dependencies are correct, and tool settings match your workload (worker counts, memory, CPU).
  • Update tooling. Bump eslint, jest, vitest, webpack, esbuild, and Node.js to recent versions. Run the same tests and measure the difference.
  • Right-size the runner. Try doubling CPU/memory. If it helps, the return is clear and immediate.
  • Add local caching. Enable build and test caching if your tooling supports it.
  • Consider remote caching. If CI time is still above five minutes and you have a mid-to-large team, a remote cache can unlock the biggest single speedup.

Watch out for the long tail

One last caveat: after you optimize the main path, a few slow outlier tasks often remain. A single integration test that takes four minutes, or a deployment step that runs sequentially when it could be split. These are rare but high-impact. Once the bulk of the pipeline is fast, profile the outliers individually. Sometimes a single fix - running tests in parallel, splitting a deployment across regions, or moving a heavy task to a later stage - can buy another 20%.

Speed up your team with a remote build cache

Cachely makes it easy to share builds across your CI and developers. Integrate with Nx or Turborepo in minutes. Free tier - no credit card.

Put a shared build cache behind your builds
Free tier, no credit card - connect Nx, Turborepo, Gradle, or Bazel in about five minutes.
Start freeSee pricing