CI runners on your own box
runnerd runs your GitHub Actions jobs on one Linux machine, each in a fresh container, as many at once as its memory holds.
Twice the runners in the same 33G
Each runner reserves what its job really peaks at, measured over its last 20 runs, instead of a fixed cap. A job that runs out of memory gets its full cap back.
How admission works4 jobs at once, longest wait 8m00s
8 jobs at once, longest wait 9m00s
Every wait says why
A queued job names what holds it: busy slots, a runner still starting, a label nothing serves. A typo in runs-on shows up before you go looking for it.
$ runnerctl status
WORKFLOW / NAME QUEUED WAITS FOR
CI / lint 5m 23s ago No profile serves the label storefornt-small
E2E / visual-diff 1m 14s ago A runner is starting for it in node 2
E2E / a11y 1m 8s ago All 2 node slots are busy
E2E / lighthouse 55s ago All 2 node slots are busyA shell in the job that failed
With record_failures = true, a failed job's container is kept. runnerctl shell opens it at the step that failed, with its files as the job left them.
$ runnerctl shell 120055991489
runnerd: the record of job 120055991489 of halyard/ledger, CI / build
It failed at step 5: Unit tests (exit code 1)
Its log shows it ran:
./gradlew test --no-daemon
shell: /usr/bin/bash -e {0}
You are in /home/runner/_work/ledger/ledger, as the job left it. Exit to remove this container.
runner@4be1c0d2a7e9:~/_work/ledger/ledger$What GitHub would have charged
$13.10 this month
runnerd prices each job at GitHub's per-minute rate, rounded up to the minute as GitHub bills, before your plan's free minutes. Public repositories on standard runners count as free.
2,184 minutes of 412 jobs at actions_linux (2-core Linux, $0.006 a minute): $13.10
9 jobs free on GitHub: on a standard runner, of a public repository
5 jobs not priced: lost, so how long they ran is not knownWatch it from anywhere
A dashboard you can install as an app on your phone, a terminal UI, and runnerctl for scripts. All three read the same daemon.
runnerd 0.0.20 1 Overview 2 History 3 Records 4 Sizing ■ live 2/6 running · 5 waiting, oldest 5m 04s · 24 h: wait p95 29s ▁▁▁▁▆▆▆▇▇█▅▆ · 3 failed · 3 insights Budget 26.25G of 33G reserved in runnerd.slice by predicted peaks ████████████▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ███████▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓ ▒ ······················ jvm 1 4.43G used jvm 2 9G node 1 node 2 4G Slots 2 running · 5 queued ■ jvm 1 CI / build halyard/ledger 7/9 Integration tes… 7m 16s 4.43G 4.42 ■ jvm 2 a warm runner for halyard/ledger, waiting for a job ■ node 1 E2E / playwright halyard/storefront 6/8 Playwright e2e 5m 19s 2.82G 4.42 ■ node 2 a runner for halyard/storefront waiting for its job ■ small 1 a warm runner for halyard/storefront, waiting for a job ■ small 2 idle Waiting 5 queued, oldest first 5m 04s CI / lint No profile serves the label storefornt-small 55s E2E / visual-diff A runner is starting for it in node 2 49s E2E / a11y All 2 node slots are busy 36s E2E / lighthouse All 2 node slots are busy 23s E2E / unit All 2 node slots are busy Repositories ────────────────────────────────────────────────────────────────────────────────── ■ halyard/ledger jvm 0 queued ■ halyard/storefront node + small 5 queued Latest jobs ─────────────────────────────────────────────────────────────────────────────────── SUCCESS CI / build halyard/ledger 11m 05s 19m 11s ago SUCCESS E2E / playwright halyard/storefront 8m 57s 1h 01m ago SUCCESS CI / build halyard/ledger 11m 05s 1h 41m ago tab pane j/k move enter open p/r pause/resume P/U all R reseed 1-4 views ? help q quit
Scroll to inspect →


Running in three steps
Install
A static binary for x86-64 Linux, into
~/.local/bin.curl -fsSL https://github.com/ayagmar/runnerd/releases/latest/download/install.sh | shPoint it at your repositories and start it
initsizes the slots to your machine, writes the config and the service, and prints any root commands to run before you start it.runnerd init halyard/ledger halyard/storefront systemctl --user daemon-reload systemctl --user enable --now runnerdSend jobs to it
In each workflow that should run here.
runs-on: [self-hosted, linux, x64]

