Skip to content
Latchkey LogoLatchkey home

Self-hosted vs managed runners for GitHub Actions

Self-hosted vs managed runners is a question about who carries the pager, not about the per-minute rate: GitHub charges nothing for self-hosted Actions usage, so on compute alone self-hosting wins every time. What you pay instead is an autoscaler that has to be right, images that drift, a disk that fills, a 30-day runner-update clock, and a machine that runs pull request code and keeps its state afterward.

Monthly bars: self-hosted $560, $464, $432 and $416 including $400 of ops, GitHub $60, Latchkey $25
Ten thousand Linux job minutes a month at 2 vCPU. Compute is the published on-demand instance rate; the ops band is a labeled assumption of four hours a month at $100 an hour, not a published figure.

A self-hosted runner is a machine you own with GitHub's runner agent installed on it. It can be anything you can boot, it sits wherever you need it, and every part of keeping it alive is yours, including the part that pages someone at 2am.

A managed runner is the same agent on somebody else's machine, sold by the minute. Blacksmith, Depot, Namespace, WarpBuild and Latchkey all deliver it identically: install an app, change one label, and the job that ran on ubuntu-latest runs on their hardware with your workflow, actions and secrets untouched.

This page is the operations and security comparison. The pure cost case, with the instance-hour arithmetic, is on self-hosted GitHub runner total cost, and the head-to-head with one vendor is on Latchkey against self-hosted runners. Everything below was read from docs.github.com or the named vendor's page on 20 September 2026.

Self-hosted against managed, on the rows that decide it (read 20 September 2026)

Self-hostedManaged vendorsManaged (Latchkey)
Price per minute, 2 vCPU LinuxNothing to GitHub: its billing page states Actions usage is free for self-hosted runners. You pay your cloud bill for every minute the host is up, not for the minutes jobs run$0.004 on Blacksmith and WarpBuild, $0.006 on Depot, $0.004 prepaid on Namespace at matched memory$0.0025
Price per minute, 4 vCPU LinuxYour instance rate. RunsOn publishes $0.0032 a minute on-demand for a 4 vCPU m7i-flex.xlarge, as your AWS cost rather than its own$0.008 on Blacksmith and WarpBuild, $0.012 on Depot$0.005
What else is on the billThe autoscaler, the image pipeline, patching, disk cleanup, log forwarding, and the hours that go into themA plan fee on some: Depot from $20 a month, Namespace from $100, Blacksmith and WarpBuild none$5 to $49 a month for the plan
Billing granularityYour cloud provider's, applied to host uptime rather than job timePer minute on most. Depot tracks by the second and bills whole minutes at the end of the monthPer minute, rounded up per job
Sizes and OSAnything you can boot and install the agent on: GPUs, bare metal, an on-premises box next to a license dongleBlacksmith and WarpBuild to 32 vCPU with arm64, Windows and macOS; Depot and Namespace to 642 to 16 vCPU, Ubuntu 24.04 on x86_64 only
Cache: type, limit, persistenceactions/cache still goes to GitHub and keeps the 10 GB per repository, unless you run a cache server. What a long-lived host gives you free is a warm local diskColocated caches and volumes: 25 GB per repo per week on Blacksmith, 25 GB included then $0.20 per GB on Depot, cache volumes from 20 GB on Namespacelatchkey-dev/cache-action@v1 in place of actions/cache, with 14-day retention and no published size cap
Docker build accelerationWhatever you build. A persistent BuildKit cache on a long-lived host is the best reason to self-host, and the usual reason its disk fillsVendor actions and remote builders: a sticky disk on Blacksmith, a BuildKit builder beside the runner on Depot, Remote Builders on by default at Namespacelatchkey-dev/docker-cache-action@v1, a registry-backed layer cache in place of the runner's local one
ConcurrencyYour fleet size and how fast the autoscaler reacts. GitHub caps registrations at 1,500 per five minutes and 10,000 runners per runner groupBlacksmith and WarpBuild state none on Linux; Namespace publishes 32 to 160 vCPU by plan20 busy runners per workspace by default, raisable
Job limits5 days of execution time, and 24 hours in the queue before the job is canceledNamespace publishes 3 hours on Developer and 5 on Team; the others do not publish one4 hours, after which the machine is terminated
Isolation between jobsYours to design. A persistent runner keeps everything the last job left behind; GitHub recommends ephemeral runners for autoscalingEphemeral by design: Depot terminates a single-tenant EC2 instance after one job, Blacksmith uses a fresh Firecracker microVM, WarpBuild fresh VMsOne job per runner, destroyed afterward
Public repositoriesGitHub recommends against it, in as many words, because a fork can run its own code on your machineThe same fork risk applies to any runner, but the machine is disposable and not yoursOrganization accounts only
Self-healing or retriesNone, and the host is now part of your failure surface as wellNone published by any of themTransient failures diagnosed and retried inside the run
Setup change requiredAgents, an autoscaler, images, patching, secrets rotation, disk cleanup, plus a labelAn app install and a runs-on labelAn app install and a runs-on label

The ops ledger, itemized from GitHub's own documentation

The case against self-hosting is usually made with hand-waving about "maintenance". It does not need to be. GitHub documents the work, and every item below is on a docs.github.com page rather than in anyone's opinion.

  • The autoscaler has to be ephemeral. GitHub recommends autoscaling with ephemeral runners and states that autoscaling with persistent runners is not recommended, because in certain cases it cannot guarantee that jobs are not assigned to persistent runners while they are shutting down.
  • Logs have to leave the machine. The runner log files for ephemeral runners must be forwarded to external storage, because the machine that holds them is gone by the time you want to read them.
  • The runner version is on a 30-day clock. Runners self-update by default; turn that off and you must update within 30 days of a release, or the Actions service stops queuing jobs to your runner.
  • Queued jobs fail quietly. A job whose label matches no available runner does not fail at queue time. It waits out the 24-hour queue timeout and is then canceled, which is a long time to find out that the autoscaler is wedged.
  • The disk is the recurring incident. The best reason to keep a host warm is the build cache on its disk, and a warm disk fills. That is the trade at the center of self-hosting: the thing that makes it fast is the thing that pages you.

Isolation, which is not a preference

GitHub's warning is unambiguous, and it is printed twice on the page about managing access to self-hosted runners: "We recommend that you only use self-hosted runners with private repositories. This is because forks of your public repository can potentially run dangerous code on your self-hosted runner machine by creating a pull request that executes the code in a workflow."

The reason is the state, not the code. A persistent self-hosted runner executes pull request code and keeps whatever that code left behind: a credential in a file, a poisoned dependency cache, a modified toolchain, a cron entry. The next job inherits all of it. Ephemeral registration is the documented answer, with --ephemeral on the agent so it de-registers after one job, or just-in-time runners from the REST API. Both work, and both leave you owning the automation that wipes the host and brings a clean one back.

Managed vendors sell that property as the default. What differs between them is what survives on purpose, which is the cache, and each publishes its own rule: Blacksmith sticky disks are shared across a repository and offer branch protection as a dashboard setting, Namespace cache volumes are shared by tag and can be restricted to commits from named branches, and Depot states that its cache is scoped per repository but not isolated by branch, so entries from the default branch and from feature branches share one namespace. Those are three different threat models, published rather than inferred, and worth reading before a vendor holds your build cache.

Speed

Not benchmarked yet. A self-hosted host is as fast as the hardware you bought and starts warm when it is long-lived; a managed runner is as fast as the vendor's hardware and starts cold unless the vendor keeps a warm pool. Neither of those is a number, and the runner benchmark report will replace this section with one workload measured on both.

What the published specifications do support: the warm disk on a long-lived host is the one speed advantage self-hosting gets for free, and it is exactly the advantage managed vendors rebuild with colocated caches and cache volumes you pay for separately.

Cost: the same 10,000 minutes, at four utilizations

Because GitHub charges nothing for self-hosted Actions usage, the compute line is whatever your cloud charges for the hours the fleet is up. Utilization, the share of fleet uptime that is actually running jobs, therefore decides the whole answer. The table prices 10,000 job minutes a month at 2 vCPU using a published on-demand figure for that shape, $0.0016 a minute for an m7i-flex.large, which RunsOn publishes on its pricing page as your AWS cost rather than its own markup.

Host minutes billedComputeOps, at the assumption belowMonthly total
Self-hosted at 100% utilization10,000$16$400$416
Self-hosted at 50%20,000$32$400$432
Self-hosted at 25%, a typical autoscaled fleet40,000$64$400$464
Self-hosted at 10%, a fleet sized for the worst hour100,000$160$400$560
GitHub-hosted, for referenceNot applicable$60 at $0.006 a minuteNone$60
Managed (Blacksmith)Not applicable$28 after the 3,000 free minutesNone$28
Managed (Latchkey)Not applicable$25 at $0.0025 a minuteNone$25 plus $5 to $49 a month for the plan, with the 2,000 to 6,000 included minutes not netted off

The assumption, labeled: four hours of engineering time a month at $100 an hour. It is the only figure on this page that nobody publishes, and it is doing most of the work, since it is between 71% and 96% of every self-hosted total. Turn it down to one hour and the totals run $116 to $260, still above GitHub-hosted at every utilization on the table. Turn it to zero, which is the honest setting for a team that already operates the fleet for other reasons, and self-hosting wins outright at $16 to $160.

That sensitivity is the finding. Self-hosted compute is cheap enough that the rate barely matters; what matters is how idle the fleet is and how many hours it eats. Both are measurable in your own account: divide job minutes by fleet uptime for the first, and count the pull requests and incidents that touched runner infrastructure last quarter for the second. Teams that do this honestly usually find the compute was the small half.

Reliability

The two models fail in ways that need different responses. A managed runner fails as a vendor incident: read a status page, wait, and if it keeps happening change the label back. A self-hosted runner fails as your infrastructure, and the list is specific: a full disk, a wedged agent, a version 30 days without an update, a spot interruption mid-job, a runner that lost communication with the server and took the run with it.

The second list is longer because the failure surface is larger, not because the hardware is worse. It is also the list that does not appear in a cost model, which is why the honest comparison is the one above: compute plus hours, with the hours labeled.

Where self-hosting wins outright

  • Hardware nobody rents to you: GPUs beyond the single Tesla T4 configuration GitHub sells, bare metal, an on-premises machine attached to a license dongle or a test rig.
  • A network position: jobs inside your own VPC reaching a database that has no public endpoint, which is the reason most regulated teams are here.
  • A compliance answer that requires the workload to run inside your own cloud account. Some managed vendors meet this halfway with a bring-your-own-cloud mode, which is a hybrid of the two columns.
  • Volume large enough that committed cloud spend beats any metered rate, combined with utilization high enough that the fleet is rarely idle. Both conditions, not one.
  • A persistent build cache worth more than the isolation it costs, on private repositories only, per GitHub's own recommendation.

Switching: the exact diff

Moving a job between the two models is one line. Moving the fleet is the part that takes a quarter, in either direction: adopting managed runners means decommissioning an autoscaler, and adopting self-hosted runners means building one.

.github/workflows/ci.yml
 jobs:
   test:
-    runs-on: [self-hosted, linux, x64]
+    runs-on: latchkey-small        # managed: an app install and this line

   gpu-eval:
     runs-on: [self-hosted, linux, gpu]   # stays: nobody rents this hardware

   integration:
     runs-on: [self-hosted, linux, vpc]   # stays: it needs the private database

The verdict

Self-host when you have a reason that survives being written down: hardware nobody rents, a network position, a compliance requirement, or utilization high enough that committed spend beats a metered rate. Those are good reasons and the ops work is the price of them. Cost on its own is not one, because the compute line is the small half of the bill and the large half is your team, unlabeled and unbudgeted, at four hours a month in the table above and rather more in the month something breaks. If the honest answer to "why are we self-hosting" is only ever the rate, managed runners are the same economics with the fleet on somebody else's pager, and the entire migration is one label per job with a one-label path back. Where we differ from every other vendor on this page is what happens after the job starts: a transient failure is diagnosed and retried on the runner instead of failing the build, which is the class of problem no amount of fleet ownership has ever fixed.

Frequently asked questions

How do ephemeral GitHub Actions runners work?
An ephemeral runner registers, accepts exactly one job, and de-registers itself. You get one by passing --ephemeral to the agent config, or by creating just-in-time runners through the REST API. GitHub recommends this for any autoscaled fleet, because it is the only way it can guarantee that a job is never assigned to a runner that is shutting down. You still own the automation that wipes the machine and brings a fresh one back.
Are self-hosted runners safe on public repositories?
GitHub recommends against it. Its documentation says to only use self-hosted runners with private repositories, because forks of a public repository can run dangerous code on your machine through a pull request. If you must, the runner has to be ephemeral and isolated, and fork pull requests should require approval before they run.
How much maintenance does a self-hosted runner fleet actually need?
The documented floor is an autoscaler built on ephemeral runners, external log forwarding, runner version updates within 30 days of each release, image maintenance, secrets rotation, and disk cleanup. The variable part is incidents: full disks, wedged agents and spot interruptions. Four hours a month is a reasonable planning assumption for a small fleet, and it is the largest line in the cost table above.
How do I migrate from self-hosted GitHub Actions runners without rewriting workflows?
Change runs-on for one job, run it, and compare. Managed runners use the same runner agent and the same images, so actions, secrets, matrix strategies and artifacts behave as before. Move jobs a few at a time, keep the self-hosted labels on the jobs that genuinely need your hardware or your network, and only decommission the fleet once nothing is pointing at it.

Related guides

References

Low utilization means paying for idle hours. Latchkey bills the minute a job runs, at $0.0025 at 2 vCPU. Start free → 30-day trial · No credit card