Ubin.io All Articles
Infrastructure & DevOps

Your CI/CD Pipeline Is Bleeding Time — Here's Where to Find the Leaks

By Ubin.io Infrastructure & DevOps
Your CI/CD Pipeline Is Bleeding Time — Here's Where to Find the Leaks

Ask any engineering team how long their CI/CD pipeline takes, and you'll usually get a shrug followed by something like, "Maybe 12 minutes? It's fine." Spoiler: it's probably not fine. And more importantly, those 12 minutes are almost certainly 20 minutes — or worse.

The uncomfortable truth is that most teams have never actually audited their deployment pipeline end-to-end. They set it up once, it mostly works, and it quietly becomes a background tax on developer velocity. Meanwhile, multiply that hidden overhead by 50 deploys a week across a team of eight engineers, and you're looking at a significant chunk of productive hours just... evaporating.

Let's talk about where the time actually goes — and what you can do about it today.

The Measurement Problem Nobody Talks About

Before you can fix anything, you need honest numbers. Most CI dashboards show you the time from when a job starts to when it finishes. What they don't show you is queue time — how long your build sat waiting for a runner to become available. On busy teams using shared infrastructure, queue time alone can add five to eight minutes to every pipeline run.

Start by pulling your actual wall-clock time: the moment a developer pushes a commit to the moment code is live in production. Include queue time, approval gates, manual steps, and any post-deploy verification. For a lot of teams, this number is genuinely shocking. One mid-sized SaaS team we've seen profiled went through this exercise and discovered their "15-minute pipeline" was actually averaging 47 minutes from push to production — mostly because of a manual Slack approval step that nobody had thought to automate.

That's not a CI/CD problem. That's a process problem wearing a CI/CD costume.

The Usual Suspects: Where Time Actually Disappears

Dependency Installation on Every Run

This one is embarrassingly common. Teams that reinstall npm packages, pip dependencies, or Go modules from scratch on every single pipeline run are leaving enormous performance gains on the table. Caching your dependency layer — properly, with cache keys tied to your lockfile hash — can cut install time from three or four minutes down to under 30 seconds.

The key word there is properly. A lot of teams have caching configured, but their cache keys are too broad (invalidating constantly) or too narrow (never hitting). Audit your cache hit rate. If it's below 70%, your caching strategy needs work.

Test Suite Sprawl

Test suites grow organically, and almost nobody prunes them. Over time, you accumulate slow integration tests, redundant coverage, and flaky tests that need multiple retries to pass. A test suite that ran in four minutes two years ago might now take 18 minutes — not because the tests got harder, but because nobody noticed the incremental creep.

Parallelizing your tests across multiple runners is the most immediate lever here. Tools like Jest's --runInBand flag (turned off), pytest-xdist, or native parallelism in GitHub Actions can split a 20-minute test run into five minutes without changing a single test. Beyond that, consider separating your fast unit tests from your slower integration and end-to-end tests. Run the fast ones on every push; run the slow ones only before merging to main.

Docker Build Inefficiency

If you're building Docker images without taking advantage of layer caching, you're rebuilding the entire image from scratch every time. The fix is mostly about ordering: put your dependency installation steps before your code copy steps. Your dependencies change far less frequently than your application code, so structuring your Dockerfile this way means only the final layers get rebuilt on most runs.

Also worth examining: are you using a base image that's way heavier than it needs to be? Switching from a full Ubuntu base to a slim or Alpine variant can cut image build time significantly, plus your resulting image is smaller and faster to push.

Sequential Jobs That Could Run in Parallel

This is the bottleneck that's hardest to see without drawing your pipeline on a whiteboard. Many teams have a linear pipeline: lint → test → build → deploy. But lint and test can almost always run in parallel. If your test suite takes 12 minutes and your linter takes 3, running them sequentially means you're waiting 15 minutes when you could be waiting 12.

Map out your dependency graph. Which jobs actually need to wait for other jobs to complete? You'll often find that 30–40% of your pipeline steps have no real dependencies on each other and could be running simultaneously.

Real-World Wins: What Optimization Actually Looks Like

A backend-focused startup on the East Coast (a Golang shop running on GitHub Actions) went through a pipeline audit last year and found three specific issues: no dependency caching, sequential test and lint jobs, and Docker builds that ignored layer caching. After fixing all three over a single sprint, their average pipeline time dropped from 22 minutes to just under 7 minutes. That's roughly 15 minutes saved per deployment. At 40 deployments a week, that's 10 hours of developer-waiting time reclaimed every single week.

Another team, a React-heavy frontend shop, discovered that their Cypress end-to-end tests were running in their main pipeline on every PR — including tiny documentation changes. Moving E2E tests to a nightly schedule and only running them on PRs targeting main saved them nearly 18 minutes per PR run.

Neither of these required new tooling, new infrastructure, or heroic engineering effort. They required measurement, attention, and a willingness to question defaults.

Where to Start This Week

You don't need to overhaul everything at once. Start here:

  1. Measure your actual wall-clock deploy time — push to production, including all waiting.
  2. Check your cache hit rates for dependencies and Docker layers.
  3. Draw your pipeline job graph and identify anything sequential that could be parallel.
  4. Profile your test suite — find the 10% of tests taking 60% of the time.
  5. Audit queue time — if you're regularly waiting for runners, it's time to scale your runner capacity.

Fast pipelines aren't a luxury. They're a competitive advantage. Teams that ship confidently and quickly build better products, catch bugs sooner, and keep engineers in flow state instead of watching progress bars. The bottlenecks are there — you just have to look for them.