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.

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-hosted | Managed vendors | Managed (Latchkey) | |
|---|---|---|---|
| Price per minute, 2 vCPU Linux | Nothing 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 Linux | Your 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 bill | The autoscaler, the image pipeline, patching, disk cleanup, log forwarding, and the hours that go into them | A 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 granularity | Your cloud provider's, applied to host uptime rather than job time | Per minute on most. Depot tracks by the second and bills whole minutes at the end of the month | Per minute, rounded up per job |
| Sizes and OS | Anything you can boot and install the agent on: GPUs, bare metal, an on-premises box next to a license dongle | Blacksmith and WarpBuild to 32 vCPU with arm64, Windows and macOS; Depot and Namespace to 64 | 2 to 16 vCPU, Ubuntu 24.04 on x86_64 only |
| Cache: type, limit, persistence | actions/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 disk | Colocated 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 Namespace | latchkey-dev/cache-action@v1 in place of actions/cache, with 14-day retention and no published size cap |
| Docker build acceleration | Whatever you build. A persistent BuildKit cache on a long-lived host is the best reason to self-host, and the usual reason its disk fills | Vendor actions and remote builders: a sticky disk on Blacksmith, a BuildKit builder beside the runner on Depot, Remote Builders on by default at Namespace | latchkey-dev/docker-cache-action@v1, a registry-backed layer cache in place of the runner's local one |
| Concurrency | Your fleet size and how fast the autoscaler reacts. GitHub caps registrations at 1,500 per five minutes and 10,000 runners per runner group | Blacksmith and WarpBuild state none on Linux; Namespace publishes 32 to 160 vCPU by plan | 20 busy runners per workspace by default, raisable |
| Job limits | 5 days of execution time, and 24 hours in the queue before the job is canceled | Namespace publishes 3 hours on Developer and 5 on Team; the others do not publish one | 4 hours, after which the machine is terminated |
| Isolation between jobs | Yours to design. A persistent runner keeps everything the last job left behind; GitHub recommends ephemeral runners for autoscaling | Ephemeral by design: Depot terminates a single-tenant EC2 instance after one job, Blacksmith uses a fresh Firecracker microVM, WarpBuild fresh VMs | One job per runner, destroyed afterward |
| Public repositories | GitHub recommends against it, in as many words, because a fork can run its own code on your machine | The same fork risk applies to any runner, but the machine is disposable and not yours | Organization accounts only |
| Self-healing or retries | None, and the host is now part of your failure surface as well | None published by any of them | Transient failures diagnosed and retried inside the run |
| Setup change required | Agents, an autoscaler, images, patching, secrets rotation, disk cleanup, plus a label | An app install and a runs-on label | An 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 billed | Compute | Ops, at the assumption below | Monthly total | |
|---|---|---|---|---|
| Self-hosted at 100% utilization | 10,000 | $16 | $400 | $416 |
| Self-hosted at 50% | 20,000 | $32 | $400 | $432 |
| Self-hosted at 25%, a typical autoscaled fleet | 40,000 | $64 | $400 | $464 |
| Self-hosted at 10%, a fleet sized for the worst hour | 100,000 | $160 | $400 | $560 |
| GitHub-hosted, for reference | Not applicable | $60 at $0.006 a minute | None | $60 |
| Managed (Blacksmith) | Not applicable | $28 after the 3,000 free minutes | None | $28 |
| Managed (Latchkey) | Not applicable | $25 at $0.0025 a minute | None | $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.
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 databaseThe 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?
--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?
How much maintenance does a self-hosted runner fleet actually need?
How do I migrate from self-hosted GitHub Actions runners without rewriting workflows?
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
- GitHub self-hosted runners reference: ephemeral runners, autoscaling and the update window
- GitHub on self-hosted runner access, with the public-repository warning
- GitHub Actions billing: usage is free for self-hosted runners
- RunsOn pricing, which publishes per-minute AWS instance costs by size
- Latchkey runner sizes, rates and plans
- GitHub Actions documentation