GitHub Actions Self-Hosted Runners vs GitHub-Hosted: Cost, Security & Control

Batuhan Esirger
Batuhan Esirger
Co-Founder
Updated Oct 17, 202615 min read
Written for: Developers, DevOps teams, platform engineers, and engineering leaders comparing GitHub-hosted and self-hosted GitHub Actions runners for cost, security, private networking, and CI infrastructure control.
GitHub ActionsCI/CDSelf-Hosted RunnersDevOpsSecurityAutomationCloud VMs
Last reviewed:
Guide
GitHub Actions Self-Hosted Runners vs GitHub-Hosted: Cost, Security & Control
rafftechnologies.com/learn
On this page

Key Takeaways

GitHub-hosted runners remain the safer default for standard, bursty, public, or low-operations CI because GitHub manages runner capacity and provides a fresh environment per job. Self-hosted runners fit private-network placement, custom software or hardware, controlled runner images, or sustained workloads when the team can own patching, security, logs, isolation, and lifecycle. GitHub recommends ephemeral runners for autoscaling and warns strongly against self-hosted runners for public repositories. Compare total CI operating cost rather than runner minutes against VM price alone. Raff VMs can provide self-hosted runner compute, but the guest OS and runner remain self-managed.

A GitHub Actions self-hosted runner is a machine that your team deploys and operates to execute GitHub Actions jobs. GitHub provides the runner software and workflow orchestration; your team owns the compute, operating system, network access, tooling, patching, isolation, logging, and runner lifecycle.

For most teams, GitHub-hosted runners should remain the default. They are easier to operate, each job runs in a fresh GitHub-managed environment, and bursty workloads do not require you to keep idle CI machines online. Self-hosted runners make sense when a real requirement justifies owning the infrastructure: private-network placement, custom hardware or software, controlled images, specialized build tooling, or sustained CI usage where dedicated compute is economically reasonable.

Raff Technologies is relevant only in that second case. A Raff VM can provide the controlled compute layer for a self-hosted runner, but the runner remains self-managed infrastructure. Raff does not remove the security, patching, isolation, or CI-operations responsibilities that come with self-hosting.

GitHub-hosted vs self-hosted runners: quick verdict

Decision areaGitHub-hosted runnerSelf-hosted runner
Compute lifecycleGitHub manages itYour team manages it
Job environmentFresh instance per jobPersistent unless you deliberately make it ephemeral
ScalingGitHub handles capacityYou provision fixed capacity or autoscaling
OS/toolchain controlStandard or larger runner optionsFull machine-level control
Private network placementAvailable only through supported hosted patternsDirect placement inside your chosen network
Hardware choiceGitHub runner catalogYour VM/server configuration
Runner patchingGitHub-managedYour responsibility
Security blast radiusLower infrastructure ownershipDepends on runner isolation and reachable resources
Cost modelUsage-based, with plan allowances and runner-specific ratesInfrastructure + idle capacity + operations
Best fitStandard, bursty, public, or low-ops CIPrivate, specialized, sustained, or infrastructure-heavy CI

A self-hosted runner is not automatically faster, cheaper, or safer. It gives you more control by transferring more responsibility to your team.

What is a GitHub Actions runner?

A runner is the system that receives and executes a GitHub Actions job.

During a workflow, the runner may:

  • download repository code;
  • execute shell commands and actions;
  • access the GITHUB_TOKEN and approved secrets;
  • reach network services available from the machine;
  • build containers and artifacts;
  • use local caches;
  • upload logs and artifacts;
  • deploy to staging or production targets.

That makes the runner part of your security boundary. It is not simply disposable CPU. A compromised runner may expose source code, credentials, artifacts, network routes, or deployment access that the workflow can reach.

How runs-on chooses a runner

GitHub Actions uses runs-on to decide where a job executes.

A GitHub-hosted Linux job can use:

Yaml
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - run: npm ci - run: npm test

A self-hosted job can use:

Yaml
jobs: test: runs-on: [self-hosted, linux, x64] steps: - uses: actions/checkout@v6 - run: npm ci - run: npm test

Labels let you route jobs to runners with the required characteristics. Runner groups add another control layer by limiting which repositories can use a pool.

For example:

Text
build-runners labels: linux, x64, build staging-deploy labels: linux, staging-deploy production-deploy labels: linux, production-deploy

Do not route every workflow to one generic pool if some runners have materially more privileged network or deployment access than others.

Why GitHub-hosted runners are the safer default for most teams

GitHub-hosted runners are usually the better choice when:

  • jobs are bursty or unpredictable;
  • standard Linux, Windows, or macOS environments are sufficient;
  • the repository accepts public or untrusted contributions;
  • the team does not want to maintain runner operating systems;
  • clean job environments are valuable;
  • multiple operating systems are needed;
  • CI infrastructure is not something the team wants to operate;
  • the self-hosting requirement is based only on a vague expectation of lower cost.

GitHub currently provisions a fresh hosted runner instance for each job. That clean-instance model removes an entire category of state-persistence problems that a persistent self-hosted machine has to solve itself.

Before self-hosting, confirm that standard or larger GitHub-hosted runners do not already solve the actual constraint.

When a self-hosted GitHub Actions runner makes sense

Self-hosting is strongest when one of these requirements is explicit.

Private network access

A runner may need to reach infrastructure that should not be publicly reachable:

  • internal package registries;
  • private staging services;
  • deployment endpoints;
  • private APIs;
  • internal artifact repositories;
  • private Kubernetes control planes;
  • restricted infrastructure tooling.

Placing a runner inside a controlled network can simplify access, but it also increases the damage a compromised workflow could cause. Give the runner only the routes and credentials the job actually needs.

Custom software or machine configuration

Self-hosting can be justified when the job requires:

  • a specific OS image;
  • uncommon system packages;
  • licensed build software;
  • custom drivers;
  • high-memory configurations;
  • sustained CPU;
  • ARM or another required architecture;
  • large local scratch space;
  • Docker tooling or custom build daemons.

Sustained CI usage

Dedicated compute can become economically attractive when runners are busy enough that predictable infrastructure cost is valuable.

That is very different from a VM that remains idle most of the month.

Measure:

  • actual GitHub-hosted billable minutes;
  • job concurrency;
  • runner utilization;
  • queue time;
  • CPU and memory peaks;
  • cache hit rate;
  • engineering time spent operating the runner fleet.

Then compare total cost.

GitHub Actions runner cost: compare the whole operating model

GitHub Actions billing is not a simple “minutes are expensive, VM is cheap” comparison.

GitHub-hosted usage depends on repository type, GitHub plan, included allowances, operating system, and runner size. Standard GitHub-hosted runners are currently free for public repositories, while private repositories receive plan-specific allowances before additional usage is billed. Larger runners have their own pricing rules.

At the time this guide was reviewed, GitHub's published baseline rate for a standard Linux 2-core hosted runner was $0.006 per minute. Use GitHub's live pricing page before making a budget decision because these rates and allowances can change.

A self-hosted model instead looks like:

Text
runner compute + storage + idle capacity + OS maintenance + runner application updates + image maintenance + logging/monitoring + autoscaling automation + security controls + incident response + engineering ownership

A GitHub-hosted model looks more like:

Text
billable hosted-runner usage + larger-runner usage where required + applicable artifact/cache storage + workflow optimization effort

The fair comparison is total CI operating cost at the same reliability and security target.

When is a self-hosted runner actually cheaper?

Self-hosting can be cheaper when:

  • workloads are sustained rather than bursty;
  • the machine is highly utilized;
  • you already operate similar infrastructure;
  • the build benefits materially from local caches or mirrors;
  • a private runner is required anyway;
  • the team can automate rebuilds and replacement;
  • runner operations do not consume disproportionate engineering time.

GitHub-hosted runners can remain cheaper when:

  • workflows are irregular;
  • runner utilization would be low;
  • the team would otherwise build a new CI platform from scratch;
  • several operating systems are required;
  • security/isolation work would be significant;
  • the workload fits standard hosted runners well.

The strongest reason to self-host is a documented technical or economic constraint, not simply “a VM looks cheaper than CI minutes.”

Security is the largest self-hosted runner trade-off

The first security question is:

Who can cause code to execute on this runner, and what can that runner reach?

GitHub explicitly warns that self-hosted runners do not inherently provide an ephemeral clean VM for each job and can be persistently compromised by untrusted workflow code.

This matters because a runner can expose:

  • repository tokens;
  • referenced secrets;
  • deployment credentials;
  • SSH keys;
  • package-registry credentials;
  • Docker sockets;
  • files left by previous jobs;
  • internal services reachable from the network;
  • cloud or infrastructure APIs.

Public repositories are a special risk

GitHub recommends that self-hosted runners almost never be used for public repositories because an attacker may be able to submit a pull request that causes untrusted code to execute on the runner.

For open-source or fork-heavy workflows, GitHub-hosted runners should normally remain the default unless you have designed a very specific isolation architecture.

Private repositories are not automatically safe

Private repositories reduce exposure, but anyone or anything capable of triggering a privileged workflow still belongs in the threat model.

Review:

  • who can open or modify pull requests;
  • reusable workflows;
  • third-party actions;
  • repository permissions;
  • runner-group access;
  • secrets exposed to the job;
  • network routes available from the runner.

A self-hosted runner should have the smallest practical blast radius.

Persistent vs ephemeral self-hosted runners

A persistent runner handles many jobs over its lifetime. It is simple to understand operationally but allows state to survive unless you clean it correctly.

An ephemeral runner is registered for one job and then de-registered. Your automation can destroy or rebuild the machine afterward.

GitHub currently recommends ephemeral runners for autoscaling and does not recommend autoscaling persistent self-hosted runners.

Ephemeral runners help reduce:

  • cross-job files and caches leaking unexpectedly;
  • stale credentials on disk;
  • toolchain drift;
  • a compromised runner receiving future jobs;
  • inconsistent cleanup.

Ephemeral does not mean trusted. During its one job, the runner can still access every token, secret, route, and service made available to it.

For production ephemeral fleets, preserve runner logs outside the runner because the machine may disappear immediately after a failed or suspicious job.

Runner software updates are an operational requirement

Self-hosting also means keeping the GitHub runner application current.

By default, the runner software can update automatically. If you disable automatic updates so that you can control the runner image yourself, GitHub currently requires the runner to be updated within 30 days of a new runner release. A runner that falls outside that update window may stop receiving queued jobs, and critical security updates can require faster action.

That means a production self-hosted design needs:

  • a runner-image build process;
  • version tracking;
  • safe rollout/rollback;
  • monitoring for outdated runners;
  • a replacement process that does not depend on manually fixing individual machines.

Self-hosted runner autoscaling

Autoscaling is where self-hosted runners become a platform engineering problem rather than a single VM problem.

A production design must handle:

  1. queued jobs;
  2. capacity decisions;
  3. runner provisioning;
  4. GitHub registration;
  5. job routing;
  6. log forwarding;
  7. job completion;
  8. runner de-registration;
  9. machine destruction;
  10. failed/abandoned runner cleanup.

GitHub's recommended Kubernetes-based solution is Actions Runner Controller (ARC). ARC is appropriate when the team already has Kubernetes expertise and wants autoscaling runner scale sets in that environment.

If you do not already operate Kubernetes, adding Kubernetes only to solve CI-runner autoscaling may add more complexity than it removes. VM-based ephemeral runners can be a simpler architecture for smaller teams, but the provisioning and cleanup automation remains your responsibility.

Private network access: useful but dangerous when too broad

Private placement is one of the most valuable self-hosting features and one of the easiest ways to create a large blast radius.

Prefer:

  • dedicated runner network segments;
  • exact destination/port firewall rules;
  • separate build and deploy runners;
  • environment-specific runner groups;
  • short-lived credentials;
  • no database access unless the job genuinely requires it;
  • external runner logs;
  • runner replacement after suspicious activity.

A workflow that only needs to trigger one deployment endpoint should not automatically receive access to an entire production network.

Sizing a self-hosted runner

Do not choose a runner size from a generic CI recommendation.

Measure:

  • peak CPU;
  • memory high-water mark;
  • working-directory size;
  • Docker layer/cache growth;
  • average job duration;
  • concurrent jobs;
  • network transfer;
  • queue time.

A lint runner and a monorepo Docker build do not need the same machine.

For predictable isolation, a simple model is one active job per VM-sized runner unless you have intentionally designed and validated multi-job concurrency.

Measure workflow performance, not only VM performance

A faster VM does not automatically create a faster CI system.

Measure:

Text
queue time + provisioning time + checkout + dependency restore + build + tests + artifact upload + deployment

A persistent runner can reduce dependency restore time while increasing security and maintenance burden. An ephemeral runner can improve isolation while adding provisioning time.

Optimize total workflow duration and reliability rather than only CPU benchmark scores.

GitHub-hosted vs self-hosted decision framework

Choose GitHub-hosted runners when:

  • workflows are bursty;
  • standard images work;
  • public or untrusted pull requests are common;
  • Linux, Windows, and macOS are all needed;
  • the team wants minimal runner maintenance;
  • CI infrastructure is not a core internal capability;
  • a larger hosted runner solves the performance requirement.

Choose self-hosted runners when:

  • private-network placement is a hard requirement;
  • the workflow needs custom software or machine configuration;
  • sustained utilization justifies dedicated compute;
  • local caches/mirrors materially improve throughput;
  • compliance requires a controlled runner environment;
  • runner images can be rebuilt and replaced automatically;
  • the team can own patching, logging, isolation, and incident response.

If you cannot name the specific requirement that GitHub-hosted runners fail to meet, stay GitHub-hosted.

Running a GitHub Actions self-hosted runner on Raff

A Raff VM can provide the compute layer for a GitHub Actions self-hosted runner when self-hosting is already justified.

A simple architecture is:

Text
GitHub Actions ↓ Runner group / labels ↓ Raff VM runner ↓ Restricted private or public network path ↓ Build, test, or deployment target

Use Raff VPC when the runner needs private connectivity to other Raff workloads. Keep firewall rules and credentials narrower than the VPC itself.

Raff VMs are self-managed at the guest OS and application level, so your team remains responsible for:

  • installing and registering the GitHub runner;
  • OS patching and hardening;
  • runner software updates;
  • build tooling;
  • secret handling;
  • logs and monitoring;
  • cache cleanup;
  • runner isolation;
  • autoscaling or replacement automation;
  • incident response inside the guest environment.

Raff is a good fit when you need predictable dedicated runner compute, controlled sizing, private placement, or a VM-based runner architecture you are prepared to operate. For light or irregular CI, GitHub-hosted runners may still be the better choice.

If that runner also deploys applications to a VM, use CI/CD for VM Deployments to design build artifacts, approval gates, secrets, health checks, and rollback separately from runner placement.

Deploy a Raff VM when your GitHub Actions workload genuinely needs self-hosted compute, private placement, or custom runner infrastructure.

Self-hosted runner production checklist

Before moving a production workflow to self-hosted runners, verify:

  • The reason for self-hosting is documented.
  • Public or untrusted workflows cannot target privileged runners.
  • Runner groups separate repositories and trust levels.
  • Build and production-deploy runners are separated where appropriate.
  • The OS has a patching process.
  • Runner application updates are monitored.
  • Credentials are short-lived and least-privilege where possible.
  • Network access is limited to required destinations.
  • Runner logs survive ephemeral runner deletion.
  • Disk and cache growth are monitored.
  • Autoscaling runners are ephemeral where practical.
  • Compromised runners can be destroyed and replaced.
  • Cost is compared against actual GitHub-hosted usage.
  • Queue time and job duration are measured.

Frequently asked questions

What is a GitHub Actions self-hosted runner?

A self-hosted runner is a machine your team deploys and manages to execute GitHub Actions jobs. GitHub provides the runner software, while your team owns the compute, operating system, network, tooling, security, updates, and lifecycle.

Are GitHub Actions self-hosted runners free?

GitHub currently does not charge GitHub Actions runner-minute usage for jobs executed on self-hosted runners, but the runner is not free to operate. You still pay for compute, storage, networking, idle capacity, monitoring, maintenance, security, and engineering time.

Is a self-hosted runner cheaper than GitHub-hosted?

Sometimes. It is most likely when CI usage is sustained, the runner is well utilized, and your team already has the operational capability to maintain it. GitHub-hosted runners are often more economical for bursty workloads because there is no idle fleet or runner infrastructure to manage.

Are GitHub self-hosted runners safe for public repositories?

Usually not. GitHub recommends that self-hosted runners almost never be used for public repositories because untrusted pull-request code can potentially compromise the runner environment. GitHub-hosted runners are the safer default for public and fork-heavy workflows.

Should self-hosted runners be ephemeral?

For autoscaling, GitHub recommends ephemeral self-hosted runners and does not recommend autoscaling persistent runners. Ephemeral runners reduce cross-job persistence, but they still need narrow credentials and network access during the job.

How often do self-hosted GitHub runners need updates?

The runner can update automatically. If automatic updates are disabled, GitHub currently requires the runner to be updated within 30 days of a new runner release, and critical security releases can require faster action.

Can a self-hosted runner access a private network?

Yes. Private-network placement is a common reason to self-host. Restrict access to the exact services the workflow needs because workflow code inherits the runner's reachable network surface.

Can I run a GitHub Actions self-hosted runner on a Raff VM?

Yes. A Raff VM can provide the runner compute layer. The runner remains self-managed, so your team owns installation, updates, OS hardening, secrets, logs, caches, isolation, and runner lifecycle.

Sources

Conclusion

GitHub Actions self-hosted runners are valuable when control over compute, network placement, tooling, or sustained capacity solves a real problem.

For most teams, GitHub-hosted runners remain the better default because GitHub handles the machine lifecycle and each job receives a fresh managed environment. Self-hosting becomes a good decision only when the extra control is worth the security and operational ownership.

If self-hosting is justified, a Raff VM can provide predictable runner compute and Raff VPC can provide private connectivity to other Raff workloads. The runner itself remains your infrastructure to secure, patch, monitor, and replace.

Was this article helpful?
Batuhan Esirger
Batuhan Esirger
Co-Founder

Co-founder of Raff, focused on practical cloud infrastructure, product strategy, and helping small teams choose simpler hosting architectures.

Put it into practice

Spin up a Raff server, follow the steps above on real infrastructure, and you are live. Linux or Windows, backed by a 14-day money-back guarantee.

Frequently asked questions

What is a GitHub Actions self-hosted runner?
A self-hosted runner is a machine your team deploys and manages to execute GitHub Actions jobs. GitHub provides the runner software, while your team owns the compute, operating system, network, tooling, security, updates, and lifecycle.
Are GitHub Actions self-hosted runners free?
GitHub currently does not charge GitHub Actions runner-minute usage for jobs executed on self-hosted runners, but the runner is not free to operate. You still pay for compute, storage, networking, idle capacity, monitoring, maintenance, security, and engineering time.
Is a self-hosted runner cheaper than GitHub-hosted?
Sometimes. It is most likely when CI usage is sustained, the runner is well utilized, and your team already has the operational capability to maintain it. GitHub-hosted runners are often more economical for bursty workloads because there is no idle fleet or runner infrastructure to manage.
Are GitHub self-hosted runners safe for public repositories?
Usually not. GitHub recommends that self-hosted runners almost never be used for public repositories because untrusted pull-request code can potentially compromise the runner environment. GitHub-hosted runners are the safer default for public and fork-heavy workflows.
Should self-hosted runners be ephemeral?
For autoscaling, GitHub recommends ephemeral self-hosted runners and does not recommend autoscaling persistent runners. Ephemeral runners reduce cross-job persistence, but they still need narrow credentials and network access during the job.
How often do self-hosted GitHub runners need updates?
The runner can update automatically. If automatic updates are disabled, GitHub currently requires the runner to be updated within 30 days of a new runner release, and critical security releases can require faster action.
Can a self-hosted runner access a private network?
Yes. Private-network placement is a common reason to self-host. Restrict access to the exact services the workflow needs because workflow code inherits the runner's reachable network surface.
Can I run a GitHub Actions self-hosted runner on a Raff VM?
Yes. A Raff VM can provide the runner compute layer. The runner remains self-managed, so your team owns installation, updates, OS hardening, secrets, logs, caches, isolation, and runner lifecycle.