- ci
- pricing
- ownership
- managed-hosting
Dedicated CI Machine vs Hosted Runners: Speed, Cost and Risk
A Ryzen 9 9950X self-hosted runner vs GitHub-hosted runners. Warm caches, per-minute pricing with real numbers, break-even volumes and the security trade-offs.
By NiceRack 7 min read
Hosted CI runners are a great default. There's nothing to maintain, every job gets a clean VM, and you pay by the minute. For many repositories that's the end of the story.
For others, the clean VM is exactly the problem. Every job starts cold: it pulls the image, restores caches over the network, reinstalls dependencies and rebuilds Docker layers that haven't changed since yesterday. You pay for those minutes, and you wait for them. As a team grows, the CI bill and the time spent waiting both climb.
This article compares a dedicated machine, a Ryzen 9 9950X racked and managed for you, with GitHub-hosted runners on speed, cost, caching and security. It also covers when hosted runners are still the better choice.
The machine
The Ryzen 9 9950X dedicated CI machine comes in two memory sizes:
- CPU: AMD Ryzen 9 9950X, 16 cores / 32 threads, up to 5.7 GHz
- Memory: 64 GB DDR5 ($4,500) or 128 GB DDR5 ($7,100)
- Storage: 500 GB NVMe boot SSD, plus optional one-time add-ons: +1 TB SATA SSD $150, +2 TB SATA SSD $250, +4 TB HDD $300
- Network: dual 10 GbE
- Remote management: IPMI remote power and console
- Architecture: x86-64, Linux
Management and rack costs $99 a month. It covers power and cooling within the agreed envelope, a shared 1 Gbps port with 2 TB outbound a month, private remote access with full root, monitoring of temperature, disks and reachability, reboots on request, one approved OS reimage a month and 15 minutes of remote hands a month, with a four-business-hour response target. It's exclusive bare metal: no other customer's jobs ever run on it.
Why a warm machine is faster
A per-minute price only tells half the story. The other half is how many minutes a job takes, and a persistent machine removes whole categories of work:
- No cold start. The runner is already up. There's no VM to boot or image to pull, and the toolchain is already installed.
- Local Docker layer cache.
docker buildreuses layers from the previous build on the same disk. On a fresh VM, you either rebuild them or pull them from a registry cache over the network. - Persistent build caches. Cargo's
target/, Gradle's cache,ccacheorsccache, Bazel's disk cache,node_modulesand~/.cache/pipcan stay on local NVMe between runs. Restoring a multi-gigabyte cache archive on every job is often slower than the incremental build it enables. - Dependencies stay downloaded. Package registries and mirrors are hit once, not on every job.
- Parallelism you control. Sixteen cores and 32 threads let a single job run
make -j32or a parallel test runner. You can also split the machine into several runners if your queue is wide rather than deep.
How much faster this makes your pipeline depends entirely on the pipeline. A job that mostly sleeps on integration tests gains little. A big Rust or C++ build that currently starts from nothing every time can gain a lot. Time a few representative jobs before you assume anything.
The cost comparison
Hosted runner prices
GitHub's per-minute prices for Linux x64 runners, from GitHub's Actions runner pricing reference, as listed on 31 Oct 2026:
| Runner | Price per minute |
|---|---|
| Linux 2-core (standard) | $0.006 |
| Linux 4-core | $0.012 |
| Linux 8-core | $0.022 |
| Linux 16-core | $0.042 |
| Linux 32-core | $0.082 |
The same page says jobs are rounded up to the nearest whole minute. Larger runners (4 cores and up) need a GitHub Team or Enterprise Cloud plan and aren't free for public repositories. Paid plans include some compute usage before per-minute billing starts, and standard runners are free for public repositories (GitHub Actions billing).
Self-hosted runners are listed as free usage in the same billing document, as of 31 Oct 2026. GitHub announced and then postponed a per-minute platform charge for self-hosted runners, so check the current billing page before you plan around that staying the case.
The cost of owning
For the 64 GB machine:
| Horizon | Hardware | Management | Total | Per month |
|---|---|---|---|---|
| 24 months | $4,500 | 24 × $99 = $2,376 | $6,876 | $286.50 |
| 36 months | $4,500 | 36 × $99 = $3,564 | $8,064 | $224.00 |
For the 128 GB machine over 36 months: $7,100 + $3,564 = $10,664, or $296.22 a month. Add any storage add-on to the hardware figure. Prices exclude taxes and import duties.
Break-even minutes per month
Divide the owned cost per month (36-month horizon) by each per-minute price:
| If you'd otherwise use | 64 GB machine ($224.00/mo) | 128 GB machine ($296.22/mo) |
|---|---|---|
| 2-core at $0.006 | 37,333 min/mo | 49,370 min/mo |
| 4-core at $0.012 | 18,667 min/mo | 24,685 min/mo |
| 8-core at $0.022 | 10,182 min/mo | 13,465 min/mo |
| 16-core at $0.042 | 5,333 min/mo | 7,053 min/mo |
| 32-core at $0.082 | 2,732 min/mo | 3,612 min/mo |
For example, $224.00 ÷ $0.042 = 5,333 minutes, or about 89 hours of 16-core runner time a month.
A worked example
Take a team billing 30,000 minutes a month on 8-core larger runners, with no included minutes left to absorb them:
- Hosted: 30,000 × $0.022 = $660 a month, or 36 × $660 = $23,760 over three years.
- Owned 64 GB machine: $8,064 over three years ($8,314 with a +2 TB SATA SSD for caches).
Is one machine enough to absorb that work? 30,000 minutes of 8-core jobs is 240,000 core-minutes. A 16-core machine running all month offers 16 × 730 hours × 60 = 700,800 core-minutes. On average it fits about three times over, and warm caches should shorten those 30,000 minutes in the first place. Peaks are the catch. If everyone pushes at 5 p.m., jobs queue on one box in a way they never would on hosted runners. A common answer is to keep hosted runners as overflow, or to add a second machine.
At 5,000 minutes a month on standard 2-core runners, the sums go the other way: 5,000 × $0.006 = $30 a month, far below $224. Hosted wins easily.
Security: self-hosted runners need hygiene
A self-hosted runner executes whatever code a workflow tells it to, on a machine that persists. GitHub's own guidance is blunt: self-hosted runners don't guarantee a clean, ephemeral VM for each job, can be persistently compromised by untrusted code, and "should almost never be used for public repositories" (GitHub, secure use reference).
In practice:
- Never run untrusted fork pull requests on it. Keep public-repository and fork workflows on hosted runners, or gate them behind manual approval.
- Use ephemeral runners where you can. Register runners with
--ephemeralso each one takes a single job and is replaced, or run jobs inside a throwaway container or microVM. Keep the caches persistent and the execution environment disposable. - Scope runners narrowly. Use runner groups and labels so only the repositories that should use the machine can reach it.
- Keep secrets short-lived. Prefer OIDC-issued cloud credentials to long-lived keys stored on the box.
- Treat the cache as shared state. A poisoned cache can affect later builds. Separate caches for trusted branches from everything else.
- Patch it. You have full root, so the operating system is yours to update. Monitoring covers the hardware, not your software.
The upside of exclusive bare metal is that the only code on the machine is yours. Nobody else's workload shares the CPU or the disk.
When hosted runners are better
- Low or spiky volume. Below the break-even line, per-minute billing costs less.
- Public and open-source repositories. Standard hosted runners are free there, and running untrusted contributions on a persistent machine is the risk described above.
- Wide fan-out. Hosted runners can start dozens of jobs at once. One machine can't.
- Many operating systems. Windows and macOS runners, or arm64 alongside x64, are easier to get hosted.
- Nobody wants to own runner maintenance. We look after everything below the operating system. Runner software, OS updates and job isolation are still your team's job.
Many teams split the work: the dedicated machine handles the heavy, frequent, trusted builds where warm caches matter, and hosted runners take everything else.
Beyond CI
The same machine works as a remote dev box, including ephemeral per-branch environments, or as a small build farm with remote build execution and a shared cache. The 128 GB model is the better fit for large monorepos, heavy link steps (C++, Rust, Android) or many concurrent runners and VMs. As a rough guide, though, real needs depend on your builds.
Next steps
- Compare the Ryzen 9 9950X with 64 GB and the Ryzen 9 9950X with 128 GB.
- See pricing, including storage add-ons, and what management includes.
- Need a fleet of CI machines? See larger orders.
- Thinking about rack versus office? Read managed rack vs home lab, or contact us.
- Already have one? First steps on a Ryzen 9 9950X CI machine covers turning it into a GitHub Actions or GitLab runner.