runs-on in GitHub Actions: labels, examples, and the one line that cuts your bill
The runs-on line decides two things: the machine your job gets, and what GitHub charges for it. runs-on: ubuntu-latest bills every minute on a private repository, and a team running 20,000 minutes a month on GitHub's 4-core runner pays $222. A self-hosted label bills nothing.
Skipbox is a self-hosted runner without the upkeep. Write runs-on: skipbox and the job runs on your account's own machine for a flat $39 a month, unlimited minutes.
What runs-on takes
runs-on accepts a single label, an expression, a list of labels, or a runner group:
jobs:
hosted:
runs-on: ubuntu-latest
self-hosted:
runs-on: [self-hosted, linux, x64]
from-a-group:
runs-on:
group: build-runners
labels: ubuntu-24.04-16core
A list is AND, not OR: the runner must carry every label. A self-hosted runner gets self-hosted, its OS (linux, windows, macOS) and its architecture (x64, ARM, ARM64) by default, plus custom labels you add. A larger runner is reached by the name its organization owner gave it. If no online runner matches, the job waits in the queue, and GitHub fails it after 24 hours.
What your runs-on line costs you
On a private repository, ubuntu-latest is 2 CPUs and 8 GB at $0.006 a minute, and GitHub's 4-core runner is $0.012 a minute. Here is GitHub's 4-core runner against Skipbox's 4 vCPU plan:
| Minutes a month (4 vCPU) | GitHub | Skipbox S | You save each month |
|---|---|---|---|
| 10,000 | $102 | $39 | $63 |
| 20,000 | $222 | $39 | $183 |
| 30,000 | $342 | $39 | $303 |
| Always on | $500.40 | $39 | $461.40 |
GitHub's figures are on GitHub Team, after its included compute. Try your own minutes in the calculator on the home page.
The minutes keep climbing. Coding agents push more often than people, each new test suite adds jobs to every push, and a budget that caps the bill stops your jobs mid-month. Every line that says ubuntu-latest is a meter running.
The self-hosted line, and the work behind it
GitHub bills nothing for self-hosted runner minutes; the charge it announced in December 2025 was shelved the next day. So the cheapest runs-on line is a self-hosted one. Behind it, if you run it yourself, sits a machine you own:
- servers sized for your builds, and more of them when jobs queue up
- the OS, Docker and runner application to keep current
- cleanup between jobs, so one job's credentials and containers never reach the next
- someone to fix it when it stops taking jobs
Skipbox is that machine, run for you.
Skipbox: one label, no machine to run
A flat price for every size
runs-on | Machine | Plan, per month |
|---|---|---|
skipbox | 4 vCPU, 16 GB | S, $39 |
skipbox-8x16 | 8 vCPU, 16 GB | M, $79 |
skipbox-16x32 | 16 vCPU, 32 GB | L, $149 |
Minutes are unlimited. Skipbox registers as a self-hosted runner, so its jobs use none of your GitHub minutes and keep running when an Actions budget runs out. Jobs beyond your plan's machines wait their turn.
Faster, because the machine stays
Each plan is a VM kept for your account. Docker images, layers and build cache, package caches and the tool cache stay on its disk between jobs, so the next job starts with them. That is at least 7× the cache room GitHub gives a repository, with no cache keys to maintain.
Nothing to manage
Skipbox updates the image, wipes each job's leftovers, health-checks the machine after every job and rebuilds one that fails. The VMs run Ubuntu 24.04 with Docker, Node.js and Python.
Moving is one line
jobs:
test:
runs-on: skipbox # was ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
- Sign in to Skipbox with GitHub.
- Install the Skipbox GitHub App and pick the repositories.
- Change
runs-onand push.
In a matrix, give the Linux leg skipbox and leave Windows and macOS legs on their GitHub labels. To move a job back, change the line back.
Security
- Each account gets its own machine, for public and private repositories.
- The runner's credentials, processes and containers are wiped after every job; from 13 November 2026 the checkout and Cargo's
target/stay, so the next build is incremental. - Code from people without write access (such as pull requests from forks) runs on a fresh VM that is deleted afterwards.
Try it on your own jobs
The trial is free for 7 days or 3,000 job-minutes, whichever comes first, with no card. Change one runs-on line to skipbox, push twice, and compare the second run with what ubuntu-latest gave you.
FAQ
What does runs-on mean in GitHub Actions?
It names the machine a job runs on: a GitHub-hosted label such as ubuntu-latest, the labels of a self-hosted runner, the name of a larger runner, or a runner group. It also decides the bill, because hosted runners bill per minute and self-hosted runners don't. runs-on: skipbox puts a job on Skipbox, a self-hosted runner at a flat monthly price.
Can runs-on have multiple labels?
Yes, as a list, and a runner must carry all of them: runs-on: [self-hosted, linux, x64] matches only a runner with all three. Skipbox needs one label per job, such as skipbox or skipbox-8x16.
How do I use a self-hosted runner in runs-on?
Give the runner's labels, starting with self-hosted, for example runs-on: [self-hosted, linux, x64]. A runner service gives its machines its own labels: on Skipbox the line is runs-on: skipbox, and Skipbox runs the machine behind it.
What happens if no runner matches runs-on?
The job waits in the queue, and GitHub fails it after 24 hours. A typo in a label queues the job instead of failing it at once, so check the spelling first. On Skipbox, a job beyond your plan's machines also waits its turn and starts as soon as one is free.
How do I use runs-on with a matrix?
Put the labels in a matrix list, such as os: [skipbox, windows-latest, macos-latest], and set runs-on to the matrix's os value. GitHub starts one job per value. A Linux leg can use skipbox while Windows and macOS legs keep their GitHub labels.
Updated November 11, 2026