Engineering, architecture, and performance — straight from the builders
home/ blog/ article
DevOps

GitHub Self-Hosted vs Hosted Runners: When It's Worth Running Your Own

A practical comparison between GitHub Actions hosted runners and self-hosted runners: cost, maintenance, performance, security and control. When each one makes sense.

TT
TryTechSoftware Development

The dilemma of whoever pays the CI bill

There is a lot of talk about optimizing CI pipelines, but few people stop to look at where the bill actually comes from. GitHub Actions is generous on the free tier, with its 2,000 minutes a month on private repositories, and for a good while that is more than enough. The trouble starts when the team grows, the test suite gets heavier, builds start running in parallel, and suddenly that comfortable number turns into an invoice nobody budgeted for.

And it is not only money. It is also that feeling of waiting for a cold machine to be provisioned from scratch on every push, downloading dependencies you have already downloaded ten thousand times, just to run the same old npm ci.

That is the moment the question shows up, usually in an engineering channel at eleven at night: wouldn’t it be worth running our own runner? The answer, like almost everything in infrastructure, is a solid it depends. The goal of this post is to draw out clearly what it depends on.


What each model actually is

Before comparing, it helps to align on vocabulary, because the two models solve the same problem through very different paths.

A hosted runner is an ephemeral virtual machine that GitHub provisions, runs your job on, and discards. Every execution starts from zero, in a clean environment, with a pre-installed set of tools, and vanishes the moment it finishes. You administer nothing, update nothing, worry about neither a full disk nor an outdated kernel. In exchange, you pay per minute and accept the hardware constraints GitHub offers.

A self-hosted runner is a machine of yours, whether a server in a datacenter, a VM in the cloud, a container or even that dusty mini-PC under the desk, on which you install the GitHub Actions agent. That machine registers with the repository or organization and stands by waiting for jobs. You control the hardware, the operating system, the installed tools and, above all, the state between executions. In exchange, the maintenance is entirely yours.

The philosophical difference is exactly that: hosted is cattle, self-hosted is a pet. One you replace without a second thought, the other you nurse.


Cost: the first thing everyone looks at

The financial argument is what usually opens the conversation, so let’s face it head on. Hosted runners are billed per minute, with multipliers that vary by operating system. Linux is the cheapest option, Windows costs twice as much, and macOS can run ten times more per minute. When your pipeline depends on macOS runners to build an iOS app, the bill scales at a speed that is frankly scary.

A self-hosted runner, on the other hand, has a cost of a different nature. Instead of paying per minute consumed, you pay for idle hardware. A server running 24 hours a day costs the same whether you fire ten or ten thousand builds at it. That completely inverts the optimization logic: on hosted, every second saved is money back in your pocket, while on self-hosted what matters is keeping the machine busy enough to justify the fixed cost.

There is a crossover point, and that is what you need to calculate before any decision. Add up your monthly minutes, multiply by the per-minute cost of the operating system you use, and compare against the cost of keeping an equivalent machine running all month. For small teams with little CI volume, hosted almost always wins, because the dedicated machine would sit idle most of the time. For teams blowing through tens of thousands of minutes a month, especially on macOS or heavy builds, self-hosted starts making financial sense fast.

A detail many people forget to put in the spreadsheet: maintenance cost is also money. That hour an engineer spends debugging why the runner’s disk filled up is worth just as much as the CI minutes you saved.


Performance: where self-hosted shines

Here self-hosted has an advantage that goes beyond money, and that is usually the real reason behind the migration. Because the machine persists between executions, you can keep a genuinely warm cache. The node_modules dependencies, the already-pulled Docker images, the intermediate build artifacts, all of that stays there between one job and the next. A pipeline that takes eight minutes on a hosted runner, downloading everything from scratch, can drop to two or three minutes on a machine that already has the environment warmed up.

Add to that the freedom to pick the hardware. You can run your builds on a machine with far more cores and memory than GitHub’s standard runners offer, or plug in a local dependency cache with negligible latency, or use fast NVMe disk where hosted would hand you generic storage. For heavy compilation builds, massive test suites or machine learning pipelines that need a GPU, that difference is not marginal, it is the difference between a CI that gets in the way and a CI that helps.

Hosted runners, to be fair, have come a long way. There are larger runner options today, with more cores, and the Actions cache works reasonably well for the common cases. But you will always be limited to the hardware catalog GitHub decided to offer, and their cache, helpful as it is, still travels over the network on every execution.


Maintenance: the cost that never shows up on the invoice

Now the part nobody likes to talk about. The hosted runner has near-zero maintenance cost, and that is its greatest asset. GitHub updates the operating system, keeps the tools current, guarantees isolation between executions and handles the entire underlying infrastructure. You write the workflow and forget a machine exists underneath.

Self-hosted hands all of that work back to you, and it is more than it looks at first glance. You need to update the runner agent when GitHub ships new versions, keep the operating system patched, monitor disk and memory, clean up the cruft that piles up between builds and make sure one execution does not contaminate the next. That warm cache that gave you performance is the same thing that can give you a headache when a corrupted artifact from a previous build makes the next one fail in a way that is impossible to reproduce locally.

The strategy that solves a good part of this is to use ephemeral runners, usually in disposable containers or VMs created for a single job and destroyed right after. Tools like actions-runner-controller on Kubernetes or VM-based setups with Terraform and Ansible automate exactly that. You recover the isolation of hosted runners without giving up control, but you pay the price of keeping that orchestration running. There is no free lunch, only a choice of which bill to pay.


Security: the point where self-hosted demands respect

There is a recommendation worth repeating until it sticks: never use self-hosted runners on public repositories without rigorous isolation. The reason is simple and serious. When someone opens a pull request from a fork on a public repository, that PR’s workflow can end up executing on your machine. On a hosted runner that is harmless, because the environment is destroyed afterward. On a persistent self-hosted runner, it means arbitrary code from a stranger ran on your infrastructure, with potential access to the internal network, to cached secrets and to the host itself.

This is the kind of detail that turns a CI saving into an entry point for a security incident. If you are going to run self-hosted, do it on private repositories or with completely isolated ephemeral runners, on a segmented network, with no access to anything you are not willing to expose. The hosted model, on this front, hands you isolation out of the box, and that carries real value that rarely enters the cost calculation.


A table to close the ticket

Summing up the comparison along the axes that matter:

CriterionHostedSelf-hosted
Upfront costZeroHardware or VM
Cost by volumePer minute, scales with useFixed, regardless of use
PerformanceLimited to the catalogHardware and cache of your choice
MaintenanceZeroEntirely yours
IsolationOut of the boxYou have to build it
Security on public reposSafe by defaultRequires rigorous care
Time to first buildImmediateInitial setup needed

Final thoughts

The choice between hosted and self-hosted runners is not a question of which is better in the absolute, and be wary of anyone selling you a single answer. It is a question of where your bottleneck is. If your pain is minute costs blowing up every month, especially on macOS or heavy builds, and you have idle hardware or infra knowledge on the team, self-hosted pays for itself. If your pain is not wanting to administer one more thing, and your CI volume fits comfortably within what you pay today, hosted remains the more sensible choice, and probably the cheaper one once you add the invisible cost of maintenance.

The most mature approach I see in practice is not picking a side and planting a flag. It is using both. Hosted runners for the majority of jobs, where convenience wins, and self-hosted for the specific cases where hardware or cost justify it, such as GPU builds, giant suites or pipelines that run all day. The right question was never hosted or self-hosted, but rather which job belongs on which runner.

Love to you all.


References