Why it matters where your pipelines run
Most teams pick a CI runner once and never revisit it. The usual default is the hosted runner that comes with the CI platform, because it needs no setup and works on the first push.
That default is fine for small projects. As builds get heavier, though, the runner starts to decide how quickly developers get feedback and how large the monthly CI bill gets. It also limits how much of the build environment the team can change.
The two main options are hosted cloud runners and self-hosted runners on bare metal servers. The examples here use GitHub Actions and GitLab CI, but Jenkins, Buildkite and most other CI systems face the same trade-offs.
How hosted cloud runners work
A hosted runner is a short-lived virtual machine that your CI provider sets up for your pipeline. On GitHub Actions, each workflow run executes in a fresh, newly provisioned VM, and GitLab.com gives each job its own ephemeral VM.
You never patch an operating system or replace a broken machine, because the provider does that. Each run starts from a clean image, so one build can’t leave files, credentials or processes behind for the next. If fifty jobs arrive at once, fifty machines start. For private repositories you usually pay by the minute after an included quota, so a quiet month costs little.
For small teams, and for open-source projects that build a few times a day, that is hard to beat.
Where hosted runners start to hurt
The throwaway machine that keeps hosted runners simple is also what slows them down as pipelines grow.
A fresh machine starts with an empty disk. Docker layers, package downloads and compiler caches have to be restored from a remote cache or rebuilt. On a large monorepo or a heavy container build, that setup can take longer than the build itself.
Standard hosted runners are small virtual machines on shared hosts, so the same build can take noticeably different times from one run to the next. That makes slow feedback and flaky performance tests hard to track down. Bigger runner sizes exist, at a higher price per minute.
Per-minute billing is cheap for light use and expensive for heavy use. A team running hundreds of long builds a day can pay for the same compute many times over. Prices move, too. In December 2025 GitHub cut hosted runner prices and, in the same announcement, planned a new per-minute charge for self-hosted runners, which it postponed after pushback from users.
You also get only the operating systems, CPU types and network setup the provider offers. Special hardware, custom kernels or fixed outbound IP addresses are either unavailable or cost extra.
What self-hosted runners on bare metal give you
A self-hosted runner is the same CI agent, installed on a machine you control. On a bare metal server, that machine is a whole physical server with no other tenants.
The biggest gain for most teams is the cache. The disk stays between jobs, so Docker layers, dependency folders and build caches are already there, and a rebuild only redoes what changed. For container-heavy pipelines this usually saves more time than anything else.
Performance also stops jumping around. Every CPU core, all the memory and all the disk bandwidth belong to your jobs, so a build takes about the same time on every run and a real slowdown stands out.
You pick the hardware: high core counts for parallel compilation, plenty of RAM for large test suites, fast NVMe for I/O-heavy builds, or GPUs for model training.
Cost becomes predictable. A server costs the same each month whether it runs ten builds or ten thousand. With steady, heavy CI load, that flat price is often lower than per-minute billing, and it is far easier to budget.
Getting a machine no longer means waiting days for a rack slot. Some providers now deploy dedicated bare metal servers in minutes with hourly or monthly billing, so you can test a runner setup before you commit to it.
What you take on when you run your own
Self-hosting moves work from the CI provider to your team, and security comes first.
A self-hosted runner executes whatever code your pipelines tell it to run. GitHub’s documentation recommends using self-hosted runners only with private repositories, because forks of a public repository can run dangerous code on your runner machine through a pull request. Keep runners on their own network segment, give them only the credentials they need, and don’t reuse a production server as a build box.
A persistent runner also keeps files and processes from earlier jobs. That helps caching and hurts both security and repeatability. The usual fix is to run each job in a fresh container or short-lived VM on the server and keep the shared caches on a separate volume.
Then there is upkeep. Someone has to patch the OS, update the runner agent, rotate registration tokens, clean up disks and deal with failed hardware. None of it is hard, but it needs an owner.
Finally, a fixed server doesn’t grow on its own. When more jobs arrive than it can run, they queue, so you need either headroom for peak load or a way to overflow to extra runners.
How to decide
The right choice depends mostly on how much you build and how steady that load is.
| Your situation | Better fit | Why |
| Public open-source repository | Hosted | Clean machine per job; self-hosted runners and untrusted pull requests don’t mix |
| A few builds a day, short jobs | Hosted | Little cost, zero maintenance |
| Very spiky load (quiet, then huge bursts) | Hosted | Elastic capacity without paying for idle machines |
| Hundreds of builds a day, steady load | Bare metal | Flat cost and warm caches pay off quickly |
| Large Docker images or monorepos | Bare metal | Local layer and dependency caches cut build time |
| Need GPUs, lots of RAM or special CPUs | Bare metal | You choose the hardware |
| Need fixed IPs or private network access | Bare metal | You control the network |
Plenty of teams end up using both. The main pipelines go to bare metal runners, while hosted runners handle public repositories, rare jobs such as macOS builds, and overflow when the self-hosted pool is full. Most CI systems let you pick the runner per job with labels or tags, so the split takes one line of config.
Practical tips for bare metal runners
If you do run your own, these habits keep the setup fast and safe.
- Use ephemeral runners. In GitHub Actions, register each runner with the
- –ephemeral
- flag so it takes a single job and then de-registers. A small script can start a fresh one for the next job, or you can use Actions Runner Controller, GitHub’s Kubernetes operator that scales self-hosted runners up and down. GitLab Runner’s Docker executor gets a similar result by running every job in a new container.
- Keep caches outside the job. Put package caches and toolchains on a volume that survives between jobs, and run each job in a clean container. For Docker image builds, BuildKit can write its cache to a directory with
- –cache-to type=local,dest=<dir>
- and read it back with
- –cache-from type=local,src=<dir>
- . This needs a buildx builder or the containerd image store, not the default docker driver. You keep warm caches without leftover state.
- Label your runners and choose them per job, for example
- runs-on: [self-hosted, linux, x64, bare-metal]
- in GitHub Actions or
- tags: [bare-metal]
- in GitLab CI. A job only runs on a runner that has all the labels you list, and that is what makes the hybrid setup above work.
- Clean up on a schedule. Old images and build artifacts fill disks quickly, and a daily job that prunes unused Docker data and old workspaces prevents surprise failures.
- Track queue time as well as build time. If jobs sit waiting for a free runner, add runners or overflow to hosted ones. A runner that is busy all day is slowing your team down, even if it looks efficient on paper.
- Lock down the network. Allow only the outbound traffic builds need, keep secrets in your CI platform’s secret store, and keep build servers apart from production systems.
Conclusion
Start with hosted runners. Once builds become frequent, heavy or container-based, bare metal runners are usually faster and cheaper for the steady part of the load, provided someone owns their security and upkeep.
Before you move anything, pull a month of numbers from your CI platform: total build minutes, average queue time and how often your caches hit. If minutes are high and steady and most of each build is spent restoring caches, a bare metal runner pool is worth a trial.
I’m Rajesh Kumar, a DevOps, SRE, DevSecOps, Cloud, and Platform Engineering expert passionate about sharing practical knowledge, real-world experiences, and industry best practices. I have worked at Cotocus and regularly write about technology, travel, investing, health, product reviews, and digital marketing through my various platforms.
I publish technical articles at DevOps School, travel stories at Holiday Landmark, stock market insights at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow, and SEO and digital marketing strategies at Wizbrand.
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services — all in one place.
Explore Hospitals