Why this matters
CI is one of the few systems that spends money every time someone pushes a commit. A busy repository can burn through a plan's included minutes in a week, then keep going on metered rates nobody watches until finance asks. The waste is rarely one huge job. It is a hundred small reruns and a full browser matrix triggered by a docs typo.
The bigger cost is time. Slow pipelines push developers to batch changes, which makes failures harder to find. When a pipeline takes forty minutes, people switch tasks. When it takes six, they fix the problem while the code is fresh. Cutting CI minutes and runner costs is mostly about removing work that should never have run.
Treat the pipeline as a product with a budget. Someone owns it and it has a dashboard. Changes to it get reviewed like code. That matters more than any single caching trick.
Before you start
Get numbers before you change anything. Export job-level data for the last thirty days from your CI provider's API or usage page: workflow, job, branch, duration, queue time and status. Put it in a spreadsheet.
Sort by total minutes, not average duration. A two minute job that runs on every commit across nine matrix combinations often beats a thirty minute nightly job. Then filter for jobs that ran more than once on the same commit. That list is where the easy savings usually live.
Check what you pay for beyond compute. Artifact storage, cache storage and concurrent runner limits all show up on the bill. Note which workflows are required for merge and which are advisory. Required workflows deserve speed. Advisory ones can move to a schedule.
Write down the constraints. Tests must still pass, security scans must still run on code that ships, and releases must stay reproducible. That list stops someone from cutting a corner later.
How to do it
Do the cheap fixes first. Canceling superseded runs and adding path filters takes an afternoon and removes whole categories of work. Caching and sharding need more care because a bad cache can hide bugs.
Change one thing at a time and keep before and after numbers. A change that saves minutes but adds flaky failures is a net loss. Watch the flake rate alongside duration.
- Pull thirty days of job data. Export workflow, job, branch, duration, queue time and status from your CI provider's API or usage page. Sort by total minutes and note the top ten jobs. Save this as your baseline.
- Cancel superseded runs. Turn on concurrency groups so a new push to the same branch cancels the run still in progress. Keep the cancel setting scoped per branch or per pull request so main and release branches are never canceled mid-deploy. This removes minutes spent testing code nobody will merge.
- Filter by path and branch. Skip heavy workflows when only docs, markdown or images change. Use your provider's path filters, and keep one fast validation job that always runs. Run the full matrix on main and on pull requests that touch shared code.
- Cache dependencies with lockfile keys. Cache the package manager's download cache, not node_modules or the virtualenv. Key the cache on the lockfile hash and the OS, and save a new cache only from main. Restore keys let feature branches fall back to the main cache without writing to it.
- Split slow suites by timing. Record per test file duration, then pack files into shards so each shard takes about the same time. Static sharding by file name is simpler but drifts. Run the shard count in a matrix so the job count is easy to change.
- Right-size runners and use spot capacity. Move long, interruptible jobs like full browser suites and nightly scans to spot or preemptible capacity with retries. Keep deploy and release jobs on stable runners. Measure CPU and memory during a run before you pick a larger size, since most jobs are I/O bound.
- Build once and promote the artifact. Build the container or binary in one job, tag it with the commit SHA, and pass the reference to test, staging and production jobs. Do not rebuild per environment. This removes duplicate work and makes the deployed artifact the one that was tested.
- Quarantine flaky tests. Track tests that fail and pass on rerun of the same commit. Move them to a quarantine job that runs on a schedule and does not block merges. Fix or delete them within a set window.
- Review the pipeline monthly. Put total minutes, median duration, rerun rate and cost per merged change on one dashboard. Review it in the same meeting where you review reliability. Pipelines drift as teams add tests and jobs.
How to check it worked
Compare medians, not single builds. Pick the same workflow and branch type, then compare the median duration and total minutes for a week before and a week after. A drop in median duration with a rise in reruns means you moved the problem.
Track cost per merged pull request if you can derive it. Total CI minutes divided by merged PRs is a number that survives team growth and changing test suites.
Check that coverage did not slip. Count the tests that ran on the last ten merges to main. If that number fell, find out why. Check queue time separately from run time. Faster runners can sit in a queue while the job itself is short.
Ask the team. If developers say feedback feels faster, the change is working. If they say it feels less trustworthy, keep digging.
Mistakes to avoid
Caching everything without a key that includes the lockfile. You get stale dependencies and failures that only appear on a clean machine. Cache the package manager's download directory, not the whole project.
Path filters without a fallback. A change to a shared config or build script can affect every package. Keep one always-run job that validates the workspace and make the filters conservative.
Over-parallelizing a small suite. Splitting four minutes of tests across eight runners adds setup time and coordination. Shard only when a single job is long enough that boot and queue time is a small slice.
Self-hosted runners without an owner. They can be cheaper at steady load, but someone has to patch images, clean disks and rotate credentials. Without that, the hidden cost is an unpatched box with repository access.
Cutting security scans to save time. Run them on pull requests that touch dependencies or deploy code, and nightly for everything else.
If you want the pipeline work done in one pass, Cloudverg's Delivery platform setup is $14,500/month, fixed scope over four weeks. It covers pipelines, environments, secrets, monitoring and rollback. It fits teams deploying by hand or teams whose release process depends on one person. It is one option among others.
Frequently asked questions
Why do CI minutes keep growing when the test suite has not changed?
Growth usually comes from new jobs, expanded matrices, reruns and scheduled workflows rather than the tests themselves. Pull job-level data and sort by total minutes to find the top few. Check how many times each job ran on the same commit: reruns and duplicate triggers are common culprits.
Are self-hosted runners cheaper than hosted runners?
At steady, high utilization they can be, but the comparison only holds if you count the time spent patching images, cleaning disks, rotating credentials and handling failures. Hosted runners price in that work. Start hosted and move only predictable, long-running workloads to self-hosted capacity.
Does caching ever make a pipeline slower?
Yes. Restoring a large cache can take longer than downloading a small dependency set, and a cache miss after a restore attempt adds time. Cache the package manager's download directory rather than build output, and measure restore time against a clean install. Delete caches that rarely hit.