Self-Hosted CI Runners: When They Pay for Themselves
Hosted CI minutes are convenient until the bill arrives. Self-hosted runners are cheaper and faster, and the security model needs thinking about.
Hosted CI minutes are excellent value at low volume and stop being excellent value surprisingly quickly. A team running a few hundred builds a day on hosted runners is frequently paying more for CI than for production infrastructure.
Self-hosting is not always right, but the crossover point arrives sooner than most teams expect.
When self-hosting pays
The arithmetic is simple. Hosted CI is billed per minute at a rate that assumes a small, shared machine. A dedicated cloud server costs a fixed monthly amount and can run builds continuously.
Self-hosting starts winning when:
- Build volume is high enough that monthly hosted minutes exceed a server's cost.
- Builds are slow because the hosted runner is small — more cores and more RAM cut wall-clock time directly.
- Builds need a warm cache — dependency directories, Docker layers, compiled artefacts — that a fresh hosted runner discards every time.
- Builds need access to private networks that a hosted runner cannot reach.
- Builds need specific hardware, large disks, or unusual tooling.
This is a workload where cost per core dominates everything, which is why a Hetzner account is such a common choice for build farms. The same reasoning applies to an OVHcloud account where bandwidth is also heavy.
When hosted is still right
- Low build volume, where a server would sit idle.
- No one to maintain runners.
- Strict isolation requirements you would rather not implement yourself.
- Builds from untrusted forks in a public repository — see the security section.
Sizing a runner
Builds are usually I/O and CPU bound, in that order:
- Cores: builds parallelise well. Doubling cores frequently halves compile time. This is the one workload where buying more CPU reliably pays.
- Memory: enough for concurrent jobs plus filesystem cache. Linkers and test suites can be memory-hungry.
- Disk: fast NVMe matters more than people expect.
npm install,git checkoutand Docker layer extraction are dominated by small-file I/O. - Concurrency: one job per two to four cores is a reasonable starting ratio; measure and adjust.
A single well-specified dedicated server frequently outperforms several small hosted runners while costing less.
Caching is where the time goes
Most CI time is spent re-fetching and rebuilding things that have not changed:
- Dependency cache — persist the package manager cache directory between runs.
- Docker layer cache — use BuildKit with a persistent cache, or a registry-backed cache.
- Build artefact cache — compiler caches such as ccache or sccache.
- Container image cache — a local registry mirror so base images are not pulled from the internet on every build.
- Shallow clones — fetch the depth you need, not the entire history.
A warm cache on a self-hosted runner is the largest single speed advantage over hosted CI, and it is free.
Security: the part that needs care
Self-hosted runners are persistent machines executing code from your repository. That deserves deliberate treatment.
- Never run untrusted pull requests on a persistent self-hosted runner. A fork PR can execute arbitrary code. For public repositories, use ephemeral runners or hosted runners for fork builds.
- Use ephemeral runners — a fresh VM or container per job — wherever practical. It removes cross-build contamination entirely.
- Scope credentials tightly. A runner should hold the narrowest possible deploy credentials, ideally short-lived ones issued through OIDC federation rather than stored secrets.
- Isolate from production. Runners belong in their own network segment with no standing access to production systems.
- Patch them. They are servers, and they are frequently forgotten ones.
- Log and retain runner activity, so a compromise is investigable.
The combination of long-lived credentials on a persistent runner executing repository code is the pattern to avoid. The IAM guide covers short-lived credential patterns.
A workable architecture
| Component | Choice |
|---|---|
| Runner hosts | Dedicated cloud servers, high core count, NVMe |
| Job isolation | Container per job, or ephemeral VM |
| Cache | Persistent volume on the host, or shared object storage |
| Registry mirror | Local pull-through cache |
| Credentials | OIDC federation, no stored long-lived secrets |
| Network | Isolated segment, no standing production access |
| Scaling | Add hosts; autoscale if load is spiky |
Frequently asked questions
How many runners do I need? Start with one well-specified host and measure queue wait time. Add capacity when developers are waiting.
Can I run these on spot capacity? Yes, for interruption-tolerant jobs. Ensure jobs retry cleanly on interruption.
Do I still need hosted runners? Keep them for fork pull requests on public repositories, and as a fallback if your own runners are unavailable.
What about Windows or macOS builds? Windows works on any cloud server. macOS requires Apple hardware and is usually left hosted.
The environments guide covers the related question of ephemeral preview environments.
Related guides
Comparisons 4 min read
Hetzner vs OVHcloud: Europe’s Two Infrastructure Heavyweights
Both build their own hardware and run their own data centres. One optimises for simplicity and price, the other for breadth and reach.
Cloud Providers 4 min read
Hetzner Cloud: Price-to-Performance, Regions and the Real Constraints
Hetzner offers more compute per euro than almost anyone. Understanding where that value comes from — and what it costs you elsewhere — is how you…
Workloads 4 min read
Multi-Tenant SaaS Infrastructure: Isolation Models and Their Costs
The tenancy model is the decision everything else inherits. Choosing it late, or by accident, is how SaaS platforms end up expensive and hard to certify.
Questions about a specific configuration?
Send us the workload profile — vCPU, memory, region, open ports — and we will confirm which listings actually support it before you buy.