Ch 15 taught you what CI is, what a GitHub Actions workflow file looks like line-by-line, and how to wire a deploy on every push. Then a perfectly reasonable question shows up the moment you commit your first .github/workflows/ci.yml:
Wait — is this robot going to charge me money? It's running on somebody's computer in the cloud. Who pays for that?
This chapter answers it. By the end you'll know exactly when GitHub Actions is free and when it isn't, why the macOS runner is a budget trap, what Cloudflare's built-in "push to deploy" does instead, four ways to get the exact same safety net on your own laptop for $0, and you'll have a real price comparison table to point at when you're deciding.
The 30-Second Answer
If you're impatient, here it is:
So nobody reading this — building a side project, learning, shipping a small SaaS — needs to spend a cent on CI. The rest of the chapter is why that's true and how to stay at zero on purpose.
What You're Actually Paying For
CI isn't magic and it isn't a subscription to an idea. When GitHub Actions runs your workflow, it does a very concrete, physical thing:
It boots a brand-new virtual computer (a "runner") in a data center, clones your code onto it, runs your steps, captures the logs, then throws the computer away.
That virtual computer costs real money to run — electricity, CPU, the data center. So every CI provider charges for the same two things:
- Compute time — how many minutes your jobs spend running on their machines.
- Orchestration — the dashboard, the triggers, the secrets store, the green checkmark on your PR.
The orchestration is cheap to give away (it's just software). The compute minutes are the cost. That single fact explains every pricing page you'll ever read: they're all selling you minutes on a computer you don't own. Once you internalise that, "how do I make this free?" has an obvious answer — use a computer you already own. We'll get there.
Figure 1 — Every CI provider sells the same thing: minutes on a VM they boot and destroy for you. The free tiers are just "here are some minutes on the house."
GitHub Actions Pricing, Honestly
Here's the real shape of it. Two numbers do almost all the work: public vs private, and the OS multiplier.
Public repos: free and unlimited
If your repository is public, standard GitHub-hosted runners are free with no minute cap. This is GitHub's official, long-standing policy. Open-source projects run millions of CI minutes a month and pay nothing.
Private repos: a free monthly allowance, then per-minute
Private repos get a pool of included free minutes every month, based on your account plan:
| Plan | Included Linux minutes / month |
|---|---|
| Free | 2,000 |
| Pro | 3,000 |
| Team | 3,000 |
| Enterprise | 50,000 |
To feel how much 2,000 minutes is: a typical web-project run (npm ci + lint + test + build) takes 2–5 minutes. So the free tier is roughly 400–1,000 runs a month before you pay anything. If you push 20 times a day, every day, you're still inside it.
The multiplier is where money hides
Not all minutes cost the same. GitHub bills different operating systems at different rates — the multiplier — and it's deducted from your free pool at that rate too:
| Runner OS | Multiplier | 10-min job costs… |
|---|---|---|
| Linux (Ubuntu) | 1x | 10 minutes |
| Windows | 2x | 20 minutes |
| macOS | 10x | 100 minutes |
That macOS row is the single most expensive surprise in CI. A 10-minute Mac build burns 100 minutes of your allowance. Your 2,000 free minutes become just 200 actual macOS minutes a month. For a web project this never matters — always run on ubuntu-latest, it's the fastest and cheapest. But the moment you add an iOS or Mac app build (hello, SimpleAppShipper), the macOS runner can eat a month's quota in a couple of afternoons.
So… Do You Need GitHub Actions At All?
Step back from the price. CI's real job (from Ch 15) is: run the checklist on a clean machine so nobody has to trust that humans remembered to. The value of having that happen automatically on a server scales with how many people can break main:
| Your situation | Do you need hosted CI? |
|---|---|
| Solo side project, you're the only pusher | No. Run the checklist locally. Hosted CI is a convenience, not a requirement. |
| Solo, but you forget to test before pushing | Optional. A free git hook (below) fixes this for $0. |
| Open-source repo accepting outside PRs | Yes. You can't trust strangers' machines — the robot must run their code. And it's free (public repo). |
| 2+ developers on a private repo | Yes. This is exactly what the free tier is for. |
| You want push-to-deploy and nothing else | Maybe not — your host (Cloudflare, Vercel) can do that without a workflow file at all. Next section. |
The honest takeaway: a solo developer does not need hosted CI. You need the checklist to run, which you can do on your own machine. Hosted CI earns its keep when more than one person can push, or when you accept code you didn't write.
Alternative #1: Cloudflare's Built-In Push-to-Deploy
Here's a thing a lot of beginners don't realise: your hosting provider often has CI/CD built in, with no YAML file at all. If all you want is "push to main → my site/Worker redeploys," you may not need GitHub Actions for that part.
For this project's stack (Cloudflare Workers + Next.js on OpenNext), the relevant feature is Workers Builds: you connect your GitHub repo in the Cloudflare dashboard once, and from then on every push runs your build command and deploys the result. No wrangler deploy step to maintain, no CLOUDFLARE_API_TOKEN secret to store in GitHub — Cloudflare already has your credentials.
| Cloudflare option | Free tier | What it deploys |
|---|---|---|
| Workers Builds | 3,000 build-minutes / month, 1 concurrent build | Workers (incl. Next.js via OpenNext) |
| Pages (Git integration) | 500 builds / month, 1 concurrent build | Static sites & Pages apps |
Paid Workers Builds (on the $5/mo Workers Paid plan) raises that to 6,000 build-minutes and 6 concurrent builds, then $0.005/min beyond. For a one-person project pushing a few times a day, you will never leave the free tier.
The catch — and it's important: push-to-deploy from your host runs your build, but it doesn't give you the merge gate. There's no "block this PR until tests pass" checkmark, no per-PR status check, no running the suite on pull requests before they merge. It deploys what's already on main. So the clean division of labour is:
Figure 2 — A common real-world split: GitHub Actions does the testing/gating (free on public repos), Cloudflare does the build/deploy (free tier). You don't have to pick one — using each for what it's best at is often the cheapest setup of all.
For a solo project you can skip the gate entirely and let Cloudflare's push-to-deploy be your whole pipeline. That's a completely legitimate $0 CI/CD setup: you run the checklist locally (next section), and Cloudflare ships whatever you push.
Alternative #2: The Other Hosted CIs
You'll see these mentioned. Same model (rent minutes on their VMs), different free tiers. None is necessary for this stack, but for completeness:
| Provider | Free tier (approx.) | Best for |
|---|---|---|
| Vercel | Hobby plan free (non-commercial); preview deploy per PR | Next.js apps, preview URLs |
| Netlify | ~300 build-minutes / month | Static / JAMstack sites |
| CircleCI | Free monthly credit allowance | Teams already standardised on it |
| GitLab CI | Free monthly minutes (if code's on GitLab) | GitLab-hosted repos |
The mental model from Ch 15 transfers to all of them: a YAML checklist on a runner. Pick the one your code already lives next to.
The $0 Path: Run CI on Your Own Computer
This is the part the pricing pages never tell you, and it's the direct answer to "can I do this locally without paying?" Yes — completely. CI is just your npm scripts on a clean machine. You already own a machine. Here are four ways to use it, from simplest to most powerful.
1. Just run the checklist yourself (the honest baseline)
The lowest-tech "CI" is one command before you push:
npm run lint && npm test && npm run buildThat's it. That's the same checklist GitHub Actions runs, on the computer in front of you, for free, offline. The && means it stops at the first failure — exactly like CI going red. Make it a named script so you never have to remember the pieces:
// package.json
{
"scripts": {
"check": "npm run lint && npm test && npm run build"
}
}Now npm run check is your CI. The only thing you're missing versus hosted CI is the automatic part — you have to remember to run it.
2. A git hook — automate it for free
A git hook is a script git runs automatically at certain moments. A pre-push hook runs your checklist every time you git push, and aborts the push if anything fails. No server, no cost, no remembering:
# .git/hooks/pre-push (then: chmod +x .git/hooks/pre-push)
#!/bin/sh
echo "Running checks before push…"
npm run check || {
echo "❌ Checks failed — push aborted. Fix the errors above, then push again."
exit 1
}The one limitation: .git/hooks/ is not committed to the repo, so a teammate cloning your project won't get it. That's the entire reason Husky exists — it stores hooks in a committed .husky/ folder and points git at them, so the whole team shares the same pre-push checklist:
npm install --save-dev husky
npx husky init
echo "npm run check" > .husky/pre-pushFor a solo project the raw .git/hooks/pre-push is enough. For a team, use Husky so everyone gets the gate.
3. act — run your real GitHub Actions workflow locally
If you do have a .github/workflows/ci.yml and want to test it without pushing (and without burning minutes), act runs your actual workflow file on your machine inside Docker:
brew install act # macOS (needs Docker running)
act # run your push-triggered workflows locally
act pull_request # simulate a PR event
act -j test # run just the job named "test"This is the fastest way to debug a broken workflow — edit the YAML, run act, see the result in seconds, instead of the push → wait → red → fix → push loop. Two caveats: it needs Docker, and it can only emulate Linux runners — macos-latest and windows-latest jobs won't run under act.
4. Self-hosted runner — GitHub's orchestration, your hardware
Here's the one that genuinely makes hosted-style CI free even for heavy or macOS builds. A self-hosted runner registers your own machine with GitHub Actions. GitHub still gives you the dashboard, the triggers, the green checkmark, the secrets — all free — but the compute runs on your hardware, so there are no per-minute charges and no 10x macOS multiplier.
# In your workflow, just change runs-on:
jobs:
build:
runs-on: self-hosted # instead of ubuntu-latest / macos-latestSet it up in Repo → Settings → Actions → Runners → New self-hosted runner and run the download-and-configure script it gives you. A spare Mac mini sitting on a desk makes a perfect free macOS runner — which is exactly the kind of setup that turns "iOS builds are eating my quota" into "iOS builds cost me nothing."
The Price Comparison
Putting it all together. "Cost after free" assumes a small project; check live pricing pages before betting real money on the cents.
| Option | Free tier | Cost after free | Runs on | Best for |
|---|---|---|---|---|
Run npm run check by hand | Free forever | — | Your laptop | Solo, just starting |
| Git hook / Husky | Free forever | — | Your laptop, every push | Solo or team wanting $0 automation |
act (local Actions) | Free forever | — | Your laptop (Docker) | Debugging workflows before pushing |
| GitHub Actions — public repo | Unlimited | n/a | GitHub's Linux VMs | Open source / public code |
| GitHub Actions — private, Linux | 2,000 min/mo | ~$0.008/min | GitHub's VMs | Private team repos |
| GitHub Actions — private, macOS | counts at 10x | ~10x Linux | GitHub's Mac VMs | iOS/Mac builds (quota-hungry) |
| Self-hosted runner | Free (your hardware) | Your electricity | Your machine / Mac mini | Heavy or macOS builds, dodging the 10x |
| Cloudflare Workers Builds | 3,000 build-min/mo, 1 concurrent | $0.005/min after 6,000 (paid) | Cloudflare | Deploy Workers on push (this project) |
| Cloudflare Pages (Git) | 500 builds/mo, 1 concurrent | Paid plan for more | Cloudflare | Deploy static / Pages sites on push |
| Vercel / Netlify | Hobby free / ~300 min | Paid plans ($20/user, etc.) | Their VMs | Next.js / JAMstack hosting + CI in one |
Picking the Cheapest Thing That Fits
Figure 3 — Almost every branch lands on "free." You really have to work to spend money on CI for a normal-sized project.
What simpleappshipper.com Actually Does
To keep this honest (same as Ch 15 admitted): this project's day-to-day deploys are done by hand from a laptop — npm run cf:build && npm run cf:deploy for the Next.js site and Worker. The "CI" is the developer running the checklist locally before deploying. Cost: $0.
That's not laziness — it's the right call for a one-person project where the deployer, the developer, and the tester are the same human. The setup would graduate to hosted CI the day a second person can push to main, or the day this repo opens to outside contributors (at which point, being public, GitHub Actions would be free anyway). Until then, "$0 local" is not a compromise; it's the correct amount to spend.
Mental Model — Three Sentences
- CI providers all sell the same thing — minutes on a VM they boot and destroy for you — so the way to make CI free is to use a computer you already own.
- GitHub Actions is free and unlimited on public repos, and gives 2,000 free Linux minutes/month on private ones; the only real cost trap is the macOS runner's 10x multiplier.
- A solo developer never needs to pay: run
npm run check(optionally via a git hook) for the testing, and let your host's built-in push-to-deploy (Cloudflare Workers Builds) handle shipping — both free.
Try It Yourself (10 Minutes)
- Add a
"check": "npm run lint && npm test && npm run build"script to any project'spackage.json. Runnpm run check. That's CI, locally, for free. - Create
.git/hooks/pre-pushwith the script from this chapter,chmod +xit, and try to push with a failing test. Watch git block the push. Fix it, push again — it goes through. - Open one of your GitHub repos → Settings → Actions → Billing & usage (or the Actions usage page). Look at your free-minute balance. If the repo is public, notice there's no meter at all.
- (Optional) If you have a Cloudflare account, open a Worker → Builds → connect a GitHub repo, and push. Watch it build and deploy with no YAML file anywhere.
You now know not just how CI works, but what it costs and how to pay nothing for it.
Where This Lands in the Series
You can now write a CI pipeline, lint and test inside it, open and review PRs against it, read the file types it runs — and decide, with real numbers, whether to host it or run it yourself for free. One piece of vocabulary still sits underneath all of it: the npm install and npx commands that every CI step and every project setup begins with.
Ch 22 fills that gap and closes the operational track — what npm and npx actually are, where packages come from, and what package.json, package-lock.json, and node_modules each do. Then Part 3 opens with the question Ch 20 teed up: why JavaScript needs a framework. What React solved that vanilla <script> tags couldn't, why almost every team in 2026 builds against a framework, and why those frameworks are exactly where build steps get heavy enough that the local-vs-hosted CI tradeoff in this chapter starts to actually bite.
Ship your apps faster
When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.
Try Simple App Shipper