Tutorials Ultimate Web Development Series Chapter 21

Do You Actually Need GitHub Actions? Cost, Free Alternatives, and Running CI Locally

WebChapter 21 of the Ultimate Web Development Series24 minMay 28, 2026Beginner

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:

  1. Compute time — how many minutes your jobs spend running on their machines.
  2. 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.

Loading diagram…

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:

PlanIncluded Linux minutes / month
Free2,000
Pro3,000
Team3,000
Enterprise50,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 OSMultiplier10-min job costs…
Linux (Ubuntu)1x10 minutes
Windows2x20 minutes
macOS10x100 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 situationDo you need hosted CI?
Solo side project, you're the only pusherNo. Run the checklist locally. Hosted CI is a convenience, not a requirement.
Solo, but you forget to test before pushingOptional. A free git hook (below) fixes this for $0.
Open-source repo accepting outside PRsYes. You can't trust strangers' machines — the robot must run their code. And it's free (public repo).
2+ developers on a private repoYes. This is exactly what the free tier is for.
You want push-to-deploy and nothing elseMaybe 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 optionFree tierWhat it deploys
Workers Builds3,000 build-minutes / month, 1 concurrent buildWorkers (incl. Next.js via OpenNext)
Pages (Git integration)500 builds / month, 1 concurrent buildStatic 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:

Loading diagram…

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:

ProviderFree tier (approx.)Best for
VercelHobby plan free (non-commercial); preview deploy per PRNext.js apps, preview URLs
Netlify~300 build-minutes / monthStatic / JAMstack sites
CircleCIFree monthly credit allowanceTeams already standardised on it
GitLab CIFree 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 build

That'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-push

For 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-latest

Set 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.

OptionFree tierCost after freeRuns onBest for
Run npm run check by handFree foreverYour laptopSolo, just starting
Git hook / HuskyFree foreverYour laptop, every pushSolo or team wanting $0 automation
act (local Actions)Free foreverYour laptop (Docker)Debugging workflows before pushing
GitHub Actions — public repoUnlimitedn/aGitHub's Linux VMsOpen source / public code
GitHub Actions — private, Linux2,000 min/mo~$0.008/minGitHub's VMsPrivate team repos
GitHub Actions — private, macOScounts at 10x~10x LinuxGitHub's Mac VMsiOS/Mac builds (quota-hungry)
Self-hosted runnerFree (your hardware)Your electricityYour machine / Mac miniHeavy or macOS builds, dodging the 10x
Cloudflare Workers Builds3,000 build-min/mo, 1 concurrent$0.005/min after 6,000 (paid)CloudflareDeploy Workers on push (this project)
Cloudflare Pages (Git)500 builds/mo, 1 concurrentPaid plan for moreCloudflareDeploy static / Pages sites on push
Vercel / NetlifyHobby free / ~300 minPaid plans ($20/user, etc.)Their VMsNext.js / JAMstack hosting + CI in one

Picking the Cheapest Thing That Fits

Loading diagram…

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 laptopnpm 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

  1. 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.
  2. 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.
  3. 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)

  1. Add a "check": "npm run lint && npm test && npm run build" script to any project's package.json. Run npm run check. That's CI, locally, for free.
  2. Create .git/hooks/pre-push with the script from this chapter, chmod +x it, and try to push with a failing test. Watch git block the push. Fix it, push again — it goes through.
  3. Open one of your GitHub repos → SettingsActionsBilling & usage (or the Actions usage page). Look at your free-minute balance. If the repo is public, notice there's no meter at all.
  4. (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.

Ch 20: JSX, TSX, and npm ScriptsCh 22: What Is npm and npx?
Production WebProduction Web Apps SeriesProduction patterns for web apps: caching, rate limiting, webhooks, queues, cron jobs, and idempotency.Astro + Next.jsAstro & Next.js SeriesStatic and hybrid web app patterns with Astro, Next.js, MDX, dynamic routes, and Cloudflare deploys.CloudflareCloudflare Feature FocusFocused Cloudflare tutorials for Workers, R2, Stream, Durable Objects, and edge deployment.

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
5 free articles remainingSubscribe for unlimited access