GitHub Actions 1800 of 2000 free minutes burned across three repos, self-hosted Oracle ARM runner escape, AutoKaam operator read
OPERATOR READ · COVER · JUN 1, 2026 · ISSUE LEAD
OPERATOR READ·Jun 1, 2026·7 MIN

I Burned 90% Of GitHub's Free CI Minutes. Here's The Escape.

A real multi-repo empire eats 2000 free Actions minutes a month. When you hit zero, deploys stop firing silently. The fix is not paying per minute.

By·
OPERATOR READJUN 1, 2026 · ADITYA SHARMA

The email said 1800 of 2000 included minutes used, 90 percent, with the cycle resetting on the first. I had not added a single new workflow that month. The empire just got busy enough to find the ceiling.

AutoKaam field measurement, May 2026

What AutoKaam Thinks
  • A multi-repo solo operator will exhaust 2000 free Actions minutes a month, not might, will. I hit 1800 of 2000 across one account with three active content repos, no macOS or Windows multiplier, ju…
  • If you set a zero-dollar spending budget, the cleanest setting, then at the limit your workflows hard-block until the first of the month. That means deploys silently stop firing and production goes…
  • The deploy that ate the most was a single workflow firing on every push, 110 runs at roughly 5.5 minutes each, about 608 minutes from one repo alone. Move that off GitHub-hosted runners to Cloudfla…
  • The permanent fix for cron pipelines is a self-hosted runner on an Oracle ARM always-free box. Billable GitHub minutes drop to zero and minutes become effectively unlimited. The cost moves from a m…
90%
Share of 2000 free monthly Actions minutes burned before cycle reset
MULTI-REPO SOLO OPERATORS + SMALL TEAMS
Named stake

Free CI minutes are a finite resource, and a real multi-repo operator will run out. Not might. Will. The only question is whether you find the ceiling on a quiet Tuesday or in the middle of a deploy you actually needed.

I found mine in an email. GitHub told me I had used 1800 of 2000 included Actions minutes for the cycle, 90 percent, with the reset landing on the first of the month. I had not added a workflow that month. I had not changed a single YAML file. The empire simply got busy enough across enough repos to walk into the limit on its own. This is the operator read on why that happens, what breaks when you run dry, and the two escapes that move the bill to zero instead of paying per minute.

The math

The free tier on a personal account is 2000 Actions minutes a month on Linux runners. That number sounds generous until you count what a portfolio of live content sites actually does in thirty days. Mine is three active repos, all building on Ubuntu, none touching the macOS or Windows runners that carry a ten-times and two-times minute multiplier. Pure Linux, the cheapest rate GitHub charges, and it still ran out.

Here is roughly where the minutes went in one cycle, audited against the Actions run history per repo.

Repo role Approx minutes Share What ate it
Content site A (deploy + daily cron) ~832 ~43% Deploy-and-notify on every push, plus a daily pipeline
Content site B (daily news cron) ~629 ~32% Daily pipeline cron, plus a Pages deploy on push
Content site C (daily news cron) ~409 ~21% Daily news cron firing a few times a day
Tests and Dependabot noise ~130 ~4% Background churn

Three repos accounted for 96 percent of the burn. The single worst offender was one deploy workflow on site A that fired on every push. Around 110 runs in the cycle at roughly 5.5 minutes each is about 608 minutes, from one workflow, on one repo. That alone is 30 percent of the entire free tier spent shipping the same site over and over.

The reason an empire hits this and a hobby repo does not is compounding. Every repo you add brings its own deploy-on-push and its own daily cron. A five-minute deploy is nothing once. The same deploy is 600 minutes when you push fifteen times a week across a busy month. A daily news pipeline at fifteen minutes a run is 450 minutes a month from one cron, before anyone has pushed anything. Stack three of those and the 2000-minute tier is not a cushion, it is a wall you arrive at on schedule.

What it actually breaks

The dangerous part is not the number. It is what GitHub does when you reach it, and how silent that is.

The clean way to run a free account is to set a zero-dollar spending limit so you never get a surprise bill. The cost of that cleanliness is that when you exhaust the included minutes, workflows do not queue and they do not warn you in the moment. They are blocked until the cycle resets on the first of the month. New runs simply do not start.

Think about what that means for a deploy-on-push setup. You merge a fix to main. Git accepts it. Your commit history shows the change landed. The deploy workflow that was supposed to build the site and push it live never runs, because you are out of minutes. Production keeps serving the old build, nothing throws an error in your face, and the only signal is a workflow you stopped watching sitting in a blocked state. For a content operation where stale prod means stale articles and broken freshness signals, that is a real cost wearing the costume of a non-event.

This is the trap. Overage at roughly eight-tenths of a cent per Linux minute is cheap if you let it bill. The clean zero-budget choice is the one that bites, because it converts a billing event into a silent deploy outage. Either way, leaving the pipelines on GitHub-hosted runners means you re-fight this every single billing cycle.

The two real escapes

The fix is not buying more minutes. It is getting the heavy work off GitHub-hosted runners entirely. Two moves cover almost everything a content operator runs.

The first escape is for deploys. If you are building a static or mostly-static site, Cloudflare Pages native git builds do the build on Cloudflare's own infrastructure when you push, and that bills zero GitHub Actions minutes. You connect the repo to a Pages project once, and CF Pages watches the branch and builds it there. The 608-minute deploy workflow that was the single biggest line on my bill goes to zero billable GitHub minutes, because GitHub is no longer doing the build. As a cheap insurance step even before you migrate, add a concurrency group with cancel-in-progress to any push-triggered workflow so a flurry of commits collapses into one run instead of stacking five.

The second escape is for the cron pipelines, the daily-news jobs that run whether or not anyone pushes. Point those at a self-hosted runner on an Oracle ARM always-free instance. You register the runner once, label it, and target it with runs-on: [self-hosted, arm64]. Jobs that run on your own hardware bill zero GitHub minutes and have no monthly cap. An Ampere ARM box on the always-free tier handles a Next.js build in well under two minutes and a Go build in seconds, so the cron pipelines that were eating a thousand minutes a month between two repos cost nothing on the meter from the day you cut over.

Between those two, the entire 96 percent that was burning my free tier moves off the counter. Deploys go to CF Pages, crons go to the ARM runner, and the GitHub minute count stops being a thing I have to watch.

The tradeoffs

A self-hosted runner is not free in the way a managed runner is free. You move the cost from a meter you cannot see to a server you now own.

That ownership is real work. The runner is a long-lived process on a box you patch, monitor, and keep online. If the instance goes down, your CI goes down with it, and there is no GitHub support queue for that, it is on you. There is a security surface too: a self-hosted runner executing workflow code is something you keep off any repo that accepts untrusted pull requests, because a malicious PR can run arbitrary code on your machine. For a private single-operator empire that risk is contained, since nobody outside opens PRs against your content repos, but it is why GitHub itself tells you not to put self-hosted runners on public repos.

Cloudflare Pages has its own edges. The native build is opinionated, the free build-minute allotment has its own limits, and complex multi-step pipelines that do more than build-and-deploy may not fit cleanly into the Pages build model. For a static content site it is close to perfect. For a job that also runs tests, generates content, and pings five APIs, the ARM runner is the more honest home.

The point is that both escapes trade a problem you cannot control, an invisible metered ceiling that hard-stops your deploys, for a problem you can: an instance you provision once and maintain. That is a trade a real operator takes every time.

The operator verdict

Free CI minutes are a budget, not an entitlement. A single-repo developer never feels the edge. A multi-repo operator finds it on a calendar, because every repo adds a deploy-on-push and a daily cron, and those compound into a wall you hit the same week every month.

The instinct when the email arrives is to flip on billing and pay the eight-tenths of a cent per minute. Do not start there. The minutes you are paying for are minutes GitHub should never have been running in the first place. Static deploys belong on Cloudflare Pages native builds at zero Actions minutes. Cron pipelines belong on a self-hosted Oracle ARM always-free runner at zero billable minutes and no cap. You drive the bill to zero by moving the work, not by paying for it where it is.

My short-term move was triage: I disabled the cron-heavy workflows on the two repos that were not deploy-critical and left the deploy repo live, which bought slack until the reset. That is a tourniquet, not a fix. The actual fix is the structural one above, and it only has to be built once. Do it before the next email, because the next email might arrive on the day you actually need a deploy to fire.

Related