GitHub Actions hit our $100 cap. Linux CI now runs on a Hetzner VPS.
By José Antonio Mijares | 2026-08-26 | 8 min read
Hosted Actions stopped starting jobs after we blew a $100 monthly cap. Linux CI moved to a Helsinki VPS. Deploys stayed on Vercel. This is what moved, what did not, and what we will not do again.
GitHub Actions stopped starting our jobs. Not because the tests were wrong. Because the account hit a $100 monthly spend cap and GitHub refused new hosted runners.
Linux CI now runs on a Hetzner VPS in Helsinki. Production still deploys on Vercel and Supabase. The VPS is CI. It is not a deploy platform.
This is an ops note, not a product pitch. We write it because a blocked pipeline is how client work misses the week. If you run retainers and your CI dies on a cap, this is the path we took.
What broke
On 7 August 2026 around 17:00 UTC, every new hosted job died in 3 to 13 seconds with the same message: the job was not started because recent account payments failed or the spending limit needed to be increased.
We had Stop usage set to Yes. The cap resets on the 1st. Raising it was off the table.
That last healthy window (1 August to 7 August 17:00 UTC) reconstructed to about $155 a month, or about 4,489 billable minutes a week. The biggest hosted line items that week:
- test-nightly, about $40. It was wired to
push: branches: [main]. It fired 122 times a week, not 7. Median 17.5 minutes. - database-validation, about $32
- pr-checks, $23
- test, $16
- plus a pile of 1-minute-floor tax on docs, quality, risk, and edge jobs
A later 30-day reconstruction (20 July to 19 August, healthy runs only) put the hosted run-rate around $160/mo. The goal on that ticket was to get under $80/mo without raising the cap. Status: not met.
One workflow made it worse. A review-coverage job ran on issue_comment next to CodeRabbit replies. 16,199 runs in 30 days. About $121/mo. We disabled that workflow on 18 August. After that, real PR CI still sat around $110 to $125/mo on paper.
About 4,700 of 18,650 minutes a month were jobs under 60 seconds that GitHub still billed as a full minute. That is why a “2× faster hosted runner” would have saved $0 on that half. We looked at Blacksmith on 14 August and declined it. It needed the repos on a GitHub team.
Hosted CI went dark again on 13 August. Runs came back with steps=0 in about two seconds. Same spend-cap signature. As of 26 August, leftover hosted jobs still do not start.

What we moved
The box is in Hetzner’s Helsinki AZ. Ubuntu 24.04. 8 AMD EPYC cores. 15 GB RAM. Boot disk plus a 100 GB extra volume for Docker and caches. I am not going to invent the SKU name or the monthly euro price. I do not have that invoice in front of me.
Linux CI that now runs there: Ezeo, Caudal, Lanzalo, Drippy, parts of Regio, a landing repo, hex-hermes-core, and Perch.
What did not move:
- jamaklab.com’s own CI. That repo is still on
ubuntu-latest. It is still red from the spend cap. It is not on the VPS. - Two other product repos we left hosted
- Production deploys. Those are still Vercel and Supabase.
- macOS XCTest
- Android jobs that need
/dev/kvm
If a merge gate still requires a hosted check, that gate stays red. The VPS can be idle and the queue still will not drain.

How we got there
- 7 August: hosted CI dies at the cap.
- 11 August: we filed the spend ticket and trimmed a workflow. Self-hosted was out of scope until a runner existed.
- 13-14 August: started cutting jobs without moving to a GitHub team. Blacksmith declined.
- 18 August: disabled the comment-loop workflow.
- 19 August: filed the 30-day reconstruction ticket.
- 21 August: set runner vars on the first three repos.
- 22 August: five products were migrating off hosted. A few watch jobs were left hosted on purpose that day.
- 23 August: the host filled up (ENOSPC) and blocked a PR. Cause: 17 copies of the same Deno cache, about 13 GB. Cleaned. Shared Deno cache plus a disk-floor script that refuses to start a runner if free space is under 5 GB.
- 24 August: extra 100 GB volume. Runner units now use
KillMode=control-group.KillMode=processhad left orphan agents. - 26 August: leftover hosted jobs still empty. Decision: all Linux jobs go to the VPS. iOS and KVM stay hosted.
What we will not do again
Leave || ubuntu-latest as the fail-safe while Stop usage is on. A mistyped var, or one leftover pin, queues a hosted job that never gets a runner. The VPS sits idle. The required check stays pending.
Put a comment-posting workflow on issue_comment next to CodeRabbit.
Fire a 17-minute “nightly” on every push to main.
Split one job into many just to go faster. GitHub still bills a full minute per job.
Use KillMode=process on tenant runner units.
Let each job keep its own Deno cache on a shared disk.
Delete the runner variable to roll back one red check. A dead runner leaves required checks pending.
Put untrusted or fork PRs on this private shared host.
Call the VPS a deploy platform. It is CI.
What this is not
I do not have this month’s live Actions invoice. I do not have a paired before/after build-time chart. I will not invent either.
The $80 hosted-only target is still open. The thing that actually unblocked Linux CI was the box, not another trim.
Frequently Asked Questions
Did GitHub Actions get more expensive, or did we use it badly?
Both. The cap is $100 with Stop usage on. The run-rate in the last healthy week was about $155/mo. A nightly on every main push and a comment-loop next to CodeRabbit did a lot of that. Hosted leftovers still cannot start.
Is jamaklab.com CI on the VPS?
No. That repo is still ubuntu-latest and still red from the spend cap.
Did deploys move to Hetzner?
No. Production is still Vercel and Supabase. The VPS only runs Linux CI.
What still uses GitHub-hosted runners?
macOS iOS jobs, Android jobs that need KVM, and any workflow we have not moved. Those stay red while the cap is on.
Will a faster hosted runner fix a spend cap?
Not for the jobs that already finish under a minute. GitHub still bills a full minute. We declined Blacksmith because it needed a GitHub team, not because of speed.
Key takeaways
- A $100 Actions stop-usage cap takes hosted jobs dark. The error looks like a payment failure. The jobs never start.
- Measure the last healthy week before you talk about “CI is expensive.” Ours reconstructed to about $155/mo and 4,489 billable minutes.
- A “nightly” on every main push is not a nightly. Ours fired 122 times a week.
issue_commentnext to a bot is a cost loop. Ours was about $121/mo until we disabled the workflow.- Self-hosted Linux unblocked the jobs we actually moved. It did not move deploys, iOS, or jamaklab CI.
- Do not leave
|| ubuntu-latestas a fail-safe while the cap is on.