Bowse CI · GitHub Actions runners
Move CI off hosted runners without touching your steps.
Bowse is a drop-in runner pool for GitHub Actions. Swap the runs-on label, keep every step, and each job gets a clean Ubuntu microVM sized to the work.
name: teston: [push, pull_request]jobs: test: runs-on: ubuntu-latest runs-on: bowse timeout-minutes: 20 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 22, cache: npm } - run: npm ci && npm testEstimate
Run the numbers on your own CI volume.
Set job length and monthly job count, enter what you pay per minute now, and see what every Bowse size costs for the same minutes.
How this estimate works
Monthly cost is runtime minutes × a per-minute rate. The comparison row uses the rate you type, so it is only as accurate as that number — use the rate for a machine with similar vCPU and RAM.
Bowse figures exclude free allowances, credits, storage, discounts, and tax, and they assume a job runs equally long on both machines. Real runtimes differ between providers and machine sizes, so the fairest test is a week of one job on each.
Why now
Agent-written pull requests multiply your CI minutes.
A coding agent that pushes ten revisions before lunch runs your full suite ten times. Per-minute price stops being a rounding error and becomes the line item someone asks about.
01 · Your workflow
runs-on: bowse
GitHub queues the job for the Bowse runner pool, exactly like any self-hosted label.
02 · Bowse dispatcher
job event → machine
Receives the job event, mints a one-job runner token, and boots a machine of the requested size.
03 · Fresh microVM
Ubuntu 24.04 · x64
Runs the job, streams logs back to GitHub, then is destroyed along with its disk.
Caching
Warm caches, cold machines.
Every runner starts empty, but dependencies don’t have to download again. The cache endpoint your actions already call is answered by Bowse, so actions/cache and the cache: option on setup actions keep working.
- Docker layers ride the same store through BuildKit’s
type=ghabackend. - One repository can’t read another organization’s cache entries.
- Pull requests read caches saved on the default branch, the same scoping rules GitHub uses.
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm # no change needed
- run: npm ci && npm test
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=maxRunner sizes
Pick a size per job, not per account.
Put the label on the job that needs it: lint on bowse-1x2, the Docker build on bowse-4x16. Runtime includes machine preparation and cleanup.
| Size / label | vCPU | RAM | Root disk | Per minute | Per hour |
|---|---|---|---|---|---|
| 1x2bowse-1x2 | 1 | 2 GB | 16 GB | $0.0005 | $0.030 |
| 2x4bowse-2x4 | 2 | 4 GB | 32 GB | $0.0010 | $0.060 |
| 2x8defaultbowse-2x8 | 2 | 8 GB | 48 GB | $0.0012 | $0.072 |
| 4x16bowse-4x16 | 4 | 16 GB | 96 GB | $0.0024 | $0.144 |
| 8x32bowse-8x32 | 8 | 32 GB | 160 GB | $0.0048 | $0.288 |
| 16x64bowse-16x64 | 16 | 64 GB | 256 GB | $0.0096 | $0.576 |
Runtime
What a Bowse job runs on.
Supported
- Ubuntu 24.04 on x64, booted new for each job and never reused
- Six sizes, picked per job with a single label
- Docker Engine, Buildx, Compose, and service containers
- actions/cache and setup-* caching without workflow edits
- Status, logs, and re-runs in the GitHub Actions UI you already use
Not supported
- Windows or macOS runners, and Arm hosts
- GitHub’s hosted runner image — Bowse ships its own image; install anything extra in a step
- GitHub Enterprise Server — Bowse connects to github.com organizations and personal accounts
FAQ
Before you move a workflow.
Something missing? support@bowse.dev
How do I move my first job?
Create a workspace, install the Bowse GitHub App on the repositories you choose, and change runs-on on one job. The next push queues that job for Bowse; every other job keeps its current runner.
The CI quickstart walks through it step by step.
What do I change in my workflow?
One line per job. Swap runs-on: ubuntu-latest for runs-on: bowse for the default 2 vCPU / 8 GB machine, or a sized label like bowse-8x32. Steps, secrets, matrices, and reusable workflows stay as they are.
How do labels map to machines?
Each label is bowse-<vCPU>x<GB RAM>. Plain bowse is shorthand for bowse-2x8. There are six shapes, from 1×2 to 16×64; see the size table above.
What is on the runner image?
Ubuntu 24.04 x64 with the GitHub Actions runner, Git, Docker Engine with Buildx and Compose, and common language toolchains. The image reference lists what’s installed. It is not GitHub’s hosted image, so a job that assumes a specific preinstalled tool should install it.
Do I pay for time in GitHub’s queue?
No. The meter starts when the machine boots and stops when cleanup ends, so it covers boot and teardown as well as your steps. Time a job spends waiting in GitHub’s queue isn’t billed.
Is a runner ever reused between jobs?
No. A runner registers for exactly one job using a just-in-time token. When that job ends, the machine and its disk are deleted. Use the cache or artifacts to carry data forward.
Can Docker layers be cached between jobs?
Yes, through BuildKit’s GitHub Actions cache backend: cache-from: type=gha and cache-to: type=gha,mode=max with docker/build-push-action. Layers live in the same cache store as your dependency caches.
What about concurrency and timeouts?
Each workspace has a concurrency limit, shown in the console. Jobs above it wait in GitHub’s queue instead of failing. Set timeout-minutes the way you would on hosted runners; Bowse also enforces a safety ceiling per job.
What happens if a runner can’t start?
Bowse posts a setup check on the commit that says why — for example, the repository isn’t connected to a workspace, or the workspace reached its spend limit. Fix the cause and re-run the job from GitHub. Troubleshooting covers each check.
Move one job first. Watch the bill.
Pick your slowest or most frequent job, switch its label, and compare a week of runtime.