Why Your GitLab Pipelines Are Slow and What It’s Costing You
Slow pipelines get treated as a nuisance, but they are really a line item. Every wasted minute burns compute and developer attention at the same time, which stacks up costs quickly.
Where the time usually goes
Cache that never hits. Caching is the most commonly misconfigured part of GitLab CI. A cache key that changes on every run means you upload a cache nobody ever reads. Teams also conflate cache with artifacts: cache is for dependencies you want to reuse, artifacts are for outputs you need to pass forward or keep. Getting that backwards means re-downloading the same dependency tree in every job.
Stages that wait for no reason. By default, every job in a stage waits for the entire previous stage to finish. If your slowest test job holds up a later step that only needed one upstream artifact, you are paying for idle time. The needs keyword replaces rigid stage ordering with a dependency graph, so each job starts the moment its actual prerequisites are done.
Jobs that run when nothing changed. In a monorepo without rules:changes, every commit runs every job. Most of those jobs had no reason to execute.
Serial test suites. A single long test job is often the entire critical path. The parallel and parallel:matrix keywords split it across runners.
Redundant pipelines. Push three times in ten minutes and, without interruptible set and auto-cancel enabled, all three pipelines run to completion. You paid for two results nobody looked at.
Heavy images and artifacts. Multi-gigabyte base images pulled on every job, and artifacts handed between jobs that never needed them. Both situations burn transfer time. GitLab’s Dependency Proxy addresses the first and setting expire_in and trimming artifact paths addresses the second.
The cost you are not tracking
Watching your compute usage or runner infrastructure costs is only half the equation.
The larger cost is behavioral. A pipeline that takes forty minutes instead of twelve changes how people work. Developers batch changes to avoid waiting, which makes merge requests larger and harder to review. They context-switch while waiting and merges happen less often. That shows up directly in lead time for changes and deployment frequency, which means your DORA metrics degrade for reasons that have nothing to do with how your team writes code.
Where to start
Open a recent pipeline and read the job timing before changing anything. The bottleneck is usually one or two unexpected jobs rather than the whole pipeline. Fix the critical path first, then look at cache hit rates, then at what is running that should not be. Resist the urge to rewrite the whole configuration; most of the win is concentrated in a few places.
If you want help auditing pipeline performance or reworking a CI configuration that has grown past anyone’s ability to follow logically, reach out to us.
