The choice between hosted runners (GitHub-hosted, GitLab SaaS) and self-hosted runners often gets discussed as a pure cost calculation, which misses the criterion that decides most often in practice: network access, not per-minute pricing.

What a hosted runner structurally can’t reach

A GitHub-hosted or GitLab SaaS runner executes on entirely external infrastructure, with no network route into your internal network at all. A pipeline that needs to deploy to an on-premise Kubernetes cluster, query a database behind a VPN, or push to a private registry with no public exposure hits a wall: no pipeline configuration works around missing network connectivity.

# GitHub Actions: a self-hosted runner registers like any other,
# the difference is where the process actually runs
runs-on: [self-hosted, linux, internal-network]

A self-hosted runner, installed on a machine (or a Kubernetes pod) inside the network it needs to reach, solves that problem by construction: it’s already where the deployment needs to happen, no tunnel or extra exposure to negotiate.

The real cost, both directions

A hosted runner bills per minute consumed, a model that gets expensive fast for frequent or long builds (heavy compilation, extensive test suites), with no natural ceiling other than the bill itself. A self-hosted runner shifts that cost to the infrastructure running it and the operational time that goes with it: sizing, base image updates, queue management when several pipelines trigger at once. For a team that already has the infrastructure and Kubernetes skill (a cluster autoscaler provisioning ephemeral runners on demand, for instance), that marginal cost stays low; for a team starting from zero, it doesn’t.

The inverted security risk

A self-hosted runner introduces a specific risk hosted runners eliminate by construction: a CI pipeline running untrusted code (an external PR, a compromised dependency) then runs inside a network it should never have been able to reach, turning a supply-chain risk (covered in the article on SLSA provenance) into a direct network intrusion risk. Hosted runners, isolated by default, absorb that risk on your infrastructure’s behalf; self-hosted runners move it onto you, with the team responsible for isolating it correctly (a dedicated namespace, a restrictive NetworkPolicy, never a runner shared between external contributor PRs and production deployment).

The hybrid approach, most common in practice

Few organizations fully commit to one side. A common model: hosted runners for anything not touching internal systems (lint, unit tests, public image builds), self-hosted runners reserved for the scope that genuinely needs internal network access (deployment, integration tests against an internal database). That segmentation limits self-hosted runners’ exposed surface to the strict minimum, rather than making them the pipeline’s default path.

Takeaway

The criterion deciding between hosted and self-hosted runners is primarily the network access required, not the per-minute price: a hosted runner structurally cannot reach internal infrastructure, no matter what’s paid. Cost shifts, it never disappears, toward infrastructure and operational time on the self-hosted side. The security risk inverts too: a poorly isolated self-hosted runner turns a supply-chain risk into direct network access, a trade-off that gets decided from the start of a CI/CD industrialization.