all systems operational +48 697 610 212
Technical · [2026-06-17]

Kubernetes Cost Optimization with GitLab CI: Where the Budget Leaks

Five levers that genuinely cut your Kubernetes CI/CD bill: right-sizing resources, autoscaling runners to zero, spot nodes, caching and cost per project.

KUBERNETES · GITLAB · CI-CD · FINOPS · DEVOPS

Pipelines run, the cluster scales, deployments go out on their own. Then the cloud invoice arrives and someone asks why CI alone costs as much as half of production.

Kubernetes isn't expensive by nature. What's expensive is CI that nobody measures: runners standing by 24/7, jobs asking for 8 GB of RAM "just in case", two-gigabyte images, and builds with no cache. Below are five levers we use to bring that bill down — without slowing the team.

TL;DR

  • Right-sizing: requests set to real usage, not to "just in case" — the fastest win
  • Autoscale to zero: runners on the Kubernetes executor + cluster autoscaler, zero idle spend
  • Spot nodes: ephemeral CI is a perfect fit for cheap spot/preemptible machines
  • Caching and smaller images: a shorter build means fewer minutes burned — and less money
  • Per-project measurement: without cost broken down by team, you optimize blind

Where the budget really leaks

Before you start cutting, it helps to know where the money goes. In the CI/CD environments we've taken over, the cost almost always broke down the same way:

Cost sourceTypical symptom
Oversized requestsA job reserves 8 vCPU, uses 1.5 — the rest of the node sits idle
24/7 runnersA fixed pool pays for nights and weekends when nobody commits
No cacheEvery build pulls dependencies from scratch — minutes you pay for
Fat imagesPulling the image takes longer than the build itself, times hundreds of jobs

The classic case: 200 vCPU spread across a fixed host pool, of which a few percent actually works at night. You pay for the peak and use the average.

Lever 1: right-size resources

The most common waste is requests set "by eye, with a margin". Kubernetes reserves exactly what you ask for — regardless of what the job actually uses. An oversized request is capacity you pay for and nobody uses.

What we do in practice:

  • Measure real job usage (Prometheus / metrics-server) instead of guessing
  • Set the request near the median usage, the limit with a sensible margin
  • Enforce it per project with ResourceQuota and LimitRange — we covered that mechanism in a separate post: hard resource limits per project in GitLab CI

This is usually the fastest win: it changes no infrastructure, yet frees the capacity that over-provisioned reservations had been blocking.

Lever 2: autoscale runners to zero

Docker runners on fixed hosts pay the bill whether or not anyone commits. GitLab on the Kubernetes executor creates job pods on demand — and combined with the cluster autoscaler it adds and removes nodes based on the queue.

  • No jobs → the pool scales to zero (or to a small minimum for warm hours)
  • Commit spike → the autoscaler adds nodes for the duration, then releases them
  • You pay for jobs actually executed, not for machine readiness overnight

The cost is cold start — the first job after a quiet period waits tens of seconds for a node. For the vast majority of teams it's a worthwhile trade.

Lever 3: spot nodes for CI

CI is an ephemeral workload: a job either succeeds or gets retried. That makes it an ideal candidate for spot / preemptible nodes, which can be several times cheaper than on-demand.

There's one condition — the pipeline must tolerate node preemption. In practice that means idempotent jobs and retries enabled. We don't run production on spot, but we move builds and tests there without worry. An on-demand pool stays as a buffer in case spot capacity runs out.

Lever 4: caching and smaller images

Here cost and time are the same thing: every build minute is a minute you pay for. Two changes give the most:

  • Dependency caching — Maven, npm, pip or Docker layers don't have to run from scratch on every build
  • Smaller images — multi-stage builds and lean base images shorten the pull and speed up job start

In one of our migrations, multi-stage builds and layer cleanup cut image size by 87%, and build time dropped from around 6 minutes to ~30 seconds. A shorter build isn't just convenience — it's directly fewer runner minutes burned. We described it in the case study of migrating 60 applications to Kubernetes.

Lever 5: measure cost per project

You can't optimize what you can't see. As long as the CI bill is a single number, there's no telling which team drives most of the cost or where cutting makes sense.

  • A namespace and ResourceQuota per project give a natural point to attribute usage
  • Prometheus + Grafana show CPU/RAM and job time broken down by team
  • With that picture you optimize where it hurts — not where you happen to be looking

Where to start

The order we recommend: measure first (lever 5 and right-sizing), because it costs nothing and immediately shows the scale of the waste. Then autoscaling and spot — that's where the biggest structural savings live. Caching and images round it off and speed up the team along the way.

We run CI/CD on Kubernetes in production and regularly take over environments where the bill got out of hand. If your CI costs more than it should, see how we build and optimize pipelines or book a free consultation — we start with an audit and concrete numbers.

Want the same results in your infrastructure?

Get in touch — the first consultation is free.

Free consultation