Tips on how to keep ci under five minutes.

•

•

Five Minutes Is the Attention Budget

I spent three years at a logistics startup where our CI pipeline was essentially a ritual of patience. You’d push a fix, go grab a coffee, maybe start a load of laundry, and by the time you came back, you’d be greeted by a wall of red text because some unrelated integration test decided to flake out. People will tell you that you need expensive, auto-scaling build clusters or complex distributed caching layers to solve this, but they’re usually just trying to sell you a subscription. The truth about how to keep ci under five minutes isn’t about buying more compute; it’s about having the discipline to prune the junk that shouldn’t be running in the first place.

I’m not going to give you a list of trendy tools or tell you which cloud provider has the fastest ephemeral runners. Instead, I want to talk about what I’ve learned from watching builds fail at 2:00 AM and from my own mistakes in production. I’ll show you how to identify your slowest bottlenecks, how to handle test flakiness without ignoring it, and why radical simplification is almost always better than adding another layer of abstraction. This is just a record of what actually works when you’re tired and just want to ship.

Table of Contents

Stop the Bleeding How to Reduce Build Times

Stop the Bleeding How to Reduce Build Times

The first thing I usually do when a pipeline starts dragging is look at the dependency installation step. It’s the lowest-hanging fruit, but it’s also where most teams leak time. I’ve seen builds spend four minutes just downloading the same version of a heavy framework over and over again. You need to get serious about caching dependencies in CI. If your runner is pulling half the internet from scratch on every single commit, you aren’t running a pipeline; you’re running a very expensive, very slow downloader. I started using content-addressable caches for my lockfiles, and it turned a three-minute wait into a twenty-second check. It’s a small change, but those minutes add up when you’re trying to maintain momentum.

Beyond the dependencies, you have to look at how you’re actually running your code. I used to be a fan of the “clean slate” approach—running everything from scratch every time—until I realized that was just a polite way of saying “wasteful.” Moving toward incremental builds vs full builds changed my perspective on what a CI run should actually accomplish. You don’t need to re-compile the entire monolith because someone changed a comment in a utility function. By isolating what actually changed, you stop treating every tiny fix like a major release. It’s about being surgical rather than just throwing more CPU at the problem and hoping for the best.

The Cost of Full Builds When Incremental Builds Could Save You

The Cost of Full Builds When Incremental Builds Could Save You

I spent a good chunk of my time at that logistics startup watching our CI pipeline treat every single commit like it was the first time the code had ever existed. We were running full builds for every minor documentation fix or a one-line change in a README. It’s a classic trap: you set up a “reliable” pipeline that rebuilds everything from scratch to ensure consistency, but you end up paying for it in developer velocity. When you’re constantly stuck in a loop of incremental builds vs full builds, the default choice is almost always the expensive one.

The reality is that if you aren’t caching dependencies in CI, you are essentially burning money and time. I’ve seen teams spend twenty minutes just downloading the same version of a heavy library that hasn’t changed in six months. It’s not just about the minutes on the clock, though; it’s about the mental context switch. If I have to go grab a coffee or start a new task because my build is doing a full sweep of the universe, I’m not getting that focus back when the green checkmark finally appears. Moving toward a more surgical approach—where we only rebuild what actually changed—is the only way to stop the cycle of wasted cycles.

Five ways to stop treating your CI like a dumping ground

  • Stop downloading the internet every time a PR is opened. I spent a week debugging a build failure only to realize a dependency mirror was down; if you aren’t using a local cache or a dedicated artifact repository, you’re just praying to the network gods every single time you push code.
  • Parallelize or perish. If your test suite runs linearly, you aren’t building a pipeline, you’re building a bottleneck. I’ve learned that splitting tests into five concurrent buckets is worth the extra complexity of managing the orchestration.
  • Kill the “everything” test suite. There is a massive difference between a unit test that checks a logic gate and an integration test that spins up a Docker container. If you’re running the heavy stuff on every single commit, you’ve already lost the battle to keep things under five minutes.
  • Prune your Docker images like they’re costing you actual money. I’ve seen builds drag because someone decided the CI runner needed a 3GB base image containing every library known to man. Keep your images lean, keep your layers predictable, and stop pulling unnecessary bloat.
  • Watch your linting and formatting. It sounds trivial, but running a massive, unoptimized linter across the entire monorepo on every commit is a silent killer. Use tools that only check the files you actually changed—if you haven’t touched the legacy module, the linter shouldn’t be wasting your time looking at it.

The Bottom Line

If your build takes longer than five minutes, developers will stop running it locally and start pushing broken code just to “see if it works,” which is exactly how we end up with 2 AM incident calls.

Incremental builds aren’t a luxury or a “nice-to-have” optimization; they are a necessity for keeping the feedback loop tight enough that people actually respect the CI process.

Don’t optimize for the sake of a faster number on a dashboard; optimize so that the person next to you doesn’t lose their flow state—and their sanity—every time they need to merge a small fix.

The Reality Check

The Reality Check of slow CI pipelines.

At the end of the day, keeping a CI pipeline under five minutes isn’t about chasing a vanity metric or following some industry trend. It’s about reducing the friction between having an idea and knowing if it actually works. We’ve looked at how to stop the bleeding by trimming the fat, why you shouldn’t be rebuilding the entire world every time a single line of code changes, and how incremental builds act as your primary defense against a slow, bloated system. If you implement even half of these changes, you aren’t just saving minutes; you are reclaiming your focus and preventing that specific kind of developer burnout that comes from staring at a spinning progress bar while your coffee goes cold.

I’ve spent enough time in production incidents to know that complexity is a debt that always comes due, usually when you least expect it. A slow pipeline is just a form of technical debt that you pay for every single hour of the workday. Don’t wait for a massive outage or a complete team meltdown to start optimizing. Start small, treat your build process like a first-class citizen of your codebase, and write it down as you go. The goal isn’t to have the fastest build in the world; it’s to build a system that stays out of your way so you can actually get back to the work that matters.

Frequently Asked Questions

How do I actually implement incremental builds without accidentally shipping stale artifacts to production?

The fear of shipping stale artifacts is real—I’ve seen a “fast” build deploy a binary that didn’t actually include the latest dependency change. To sleep at night, you need a hard link between your build cache and your versioning. Don’t just cache `/node_modules`; hash your lockfiles and your source directory to create a unique cache key. If the hash changes, the cache is invalidated. If the hash doesn’t match, you build from scratch. No shortcuts.

At what point does the complexity of managing a distributed build cache become more expensive than just letting the pipeline run longer?

It’s a math problem, but not the kind you find in a textbook. I look at it through the lens of “maintenance tax.” If you’re spending more time debugging cache misses or fixing broken remote state than you are actually shipping code, you’ve crossed the line. Usually, that happens when your build logic is too brittle or your team is too small to justify the overhead. If the cache costs more in cognitive load than it saves in sleep, scrap it.

If I optimize my tests to be faster, how do I make sure I haven't just created a false sense of security by skipping the heavy integration suites?

This is the classic trade-off. If you trim your integration suite to save time, you’re trading certainty for speed. To avoid flying blind, I use a tiered approach. I keep the fast unit tests in the pre-commit or PR stage, but I never skip the heavy lifting entirely. I run the full, slow integration suite on a separate, asynchronous pipeline. It might take twenty minutes, but it catches the stuff the unit tests miss before it hits production.

How much of this is actually a CI problem versus just having a monolithic codebase that should have been split up years ago?

Honestly? It’s usually both. You can optimize a pipeline until it’s a work of art, but if you’re forcing a single massive monolith to re-validate everything every time someone changes a typo in a README, you’re fighting a losing battle. CI is the bandage, but the monolith is the wound. If your build logic is starting to look like a complex piece of engineering just to skip unnecessary steps, it’s time to talk about decoupling.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.