Tutorials Ultimate Web Development Series Chapter 24

Cloudflare Workers Builds vs GitHub Actions — and the Headless Mac Mini That Beats Both on Price

WebChapter 24 of the Ultimate Web Development Series27 minMay 31, 2026Beginner

Ch 21 made a promise and kept it: for a normal-sized web project, CI is free, and you have to work to spend a cent on it. Public repos are unlimited, private repos get a generous free tier, and a solo developer can run the whole checklist locally for nothing.

This chapter is about the one situation where that stops being true:

You're not just deploying a web Worker anymore. You're also building a macOS or iOS app — and the macOS runner bills at 10x. Now "free CI" has a meter on it, and the meter spins fast.

That's the SimpleAppShipper situation exactly: a Cloudflare Worker site (cheap to build) and a notarized Mac app (expensive to build). So this chapter does four concrete things. It walks through Cloudflare Workers Builds as an actual build pipeline — not the two-row table from Ch 21, the real thing. It runs that pipeline head-to-head against GitHub Actions for the identical deploy. It gives you a click-by-click tour of where the money shows up on both dashboards, and how to cap it before it surprises you. And then it makes the argument the title gives away: for anyone building Apple platforms regularly, the cheapest long-term CI is a headless Mac Mini you own — with the break-even math to prove it and a no-monitor setup guide to do it.

Two Build Robots, One Job

Before pricing, get the shapes straight. You're choosing between two different kinds of robot, and they're not really competitors — they're good at different halves of the job.

Cloudflare Workers BuildsGitHub Actions
What it isA built-in "push → build → deploy my Worker" pipelineA general-purpose "run any checklist on a VM" engine
ConfigA few fields in the Cloudflare dashboard — no YAMLA .github/workflows/*.yml file you write
Runs onCloudflare's Linux builders onlyLinux, Windows, or macOS runners
Knows your credentials?Yes — it's your Cloudflare account alreadyNo — you store secrets yourself
Can gate a PR?No. It deploys what's on the branchYes. The green check blocks merge
Can build an iOS/Mac app?No — Linux can't run XcodeYes, on a macOS runner (at 10x)

The last row is the whole chapter in one line. Workers Builds is perfect for the web half of this project and physically cannot touch the Apple half. GitHub Actions can do both — but the moment it touches the Apple half, the price changes character. Hold that thought; we'll come back to it after the tour.

Cloudflare Workers Builds, Properly

Ch 15 deployed the Worker the manual way (npx wrangler deploy) and Ch 21 mentioned Workers Builds in passing. Here's the real feature.

Workers Builds is Cloudflare's git integration for Workers. You connect your GitHub (or GitLab) repository once in the dashboard, and from then on every push to your chosen branch runs a build command on Cloudflare's infrastructure and deploys the result. There is no workflow file — no YAML to write, no CLOUDFLARE_API_TOKEN to generate and paste into GitHub, because Cloudflare already is your account.

Setting it up (the whole thing)

  1. Dashboard → Compute (Workers) → your Worker → SettingsBuild.
  2. Connect your GitHub repo and pick the production branch (e.g. main).
  3. Set three fields:
FieldFor this project (Next.js on OpenNext)
Build commandnpm run cf:build
Deploy commandnpm run cf:deploy (or npx wrangler deploy)
Root directorywebsite-next (the build lives in a subfolder)

That's it. Push to main, and Cloudflare clones the repo, runs npm ci, runs your build command, runs your deploy command, and your site is live at the edge. The exact commands SimpleAppShipper runs by hand today — npm run cf:build && npm run cf:deploy — become the thing the robot runs for you.

Loading diagram…

Figure 1 — Workers Builds is push-to-deploy with no YAML. You configure three fields once; Cloudflare runs your build + deploy on every push. The only metered part is the middle box: build-minutes.

What a "build-minute" is, and what it costs

A build-minute is wall-clock time Cloudflare's builder spends running your build — clone, npm ci, cf:build, deploy. A Next.js-on-OpenNext build is typically a couple of minutes. The pricing:

PlanIncluded build-minutes / monthConcurrent buildsOverage
Free3,0001
Workers Paid ($5/mo)6,0006$0.005/min

To feel the scale: at ~3 minutes a build, 3,000 free minutes is ~1,000 deploys a month. A one-person project pushing a dozen times a day uses maybe 5% of that. You will not leave the free tier for the web side. Workers Builds is, for practical purposes, free for this project — which is exactly why it stays in the picture even after we buy hardware.

The Same Job on GitHub Actions

Now the head-to-head. Here's the identical deploy — push to main, build, ship the Worker — written as a GitHub Actions workflow. This is the Ch 15 pattern, condensed:

# .github/workflows/deploy.yml
name: Deploy Worker
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest          # 1x multiplier — the cheap one
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: "20", cache: "npm" }
      - run: npm ci
      - run: npm run cf:build
      - run: npx wrangler deploy
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

Functionally it lands in the same place as Workers Builds. The differences are all in the cost shape:

For web deploys, GitHub Actions on Linux and Workers Builds are both effectively free. So if it were only the web, this would be a coin-flip and Ch 21 already told you to flip it. It isn't only the web.

Head-to-Head: the Web Deploy

Workers BuildsGitHub Actions (Linux)
Setup effort3 dashboard fieldsWrite + maintain a YAML file
Secrets to manageNoneCLOUDFLARE_API_TOKEN + account ID
Free tier3,000 build-min/moUnlimited (public) / 2,000 min (private)
Overage$0.005/min (paid plan)~$0.008/min (private Linux)
PR merge gateNoYes
Realistic monthly cost here$0$0

The honest verdict for the web side: use Workers Builds for the deploy (zero secrets, zero YAML, free), and add a tiny GitHub Actions workflow only if you want the PR test-gate. Both cost nothing. This is the "use each for what it's best at" split from Ch 21, applied.

Now the part that actually costs money.

Where the Money Actually Shows Up

You asked where to look — here are the exact paths. Bookmark both. The single biggest reason CI bills surprise people is that nobody ever opened these two pages until the email arrived.

Cloudflare: build-minutes and the bill

What you want to seeClick path
Build minutes used this monthDashboard → Compute (Workers) → your Worker → Settings → Build (build history + duration per build)
Account-wide usage vs includedManage Account → Billing → Billing & Usage
Your plan + what's meteredCompute (Workers) → Plans

Each build in the history shows its duration — that's your build-minute meter. Add up a week, multiply by your push rate, compare to 3,000. You'll almost certainly be at a few percent.

GitHub: Actions minutes, by OS

This is the page that matters most, because it's where the 10x hides:

What you want to seeClick path
Actions minutes used this monthYour avatar → Settings → Billing and licensing → Plans and usage → Usage this month → expand Actions
Minutes broken down by runner OSSame page — Linux / Windows / macOS are listed separately, multiplier already applied
Your hard spending capSettings → Billing → Spending limits → Actions

When you open that GitHub usage page on a project that builds macOS, the macOS row is the one that's eating the pie. A 20-minute Mac build shows up as 200 minutes consumed. That's not a bug — it's the multiplier doing exactly what Ch 21 warned about. Which brings us to the thing neither cloud can make cheap.

The macOS Problem Neither Cloud Solves Cheaply

Here's the wall. Cloudflare's builders are Linux — they cannot build a Mac or iOS app at all (no Xcode). So for the Apple half of the project, Workers Builds isn't even an option. That leaves macOS CI, and macOS CI is expensive everywhere, because Apple only licenses macOS to run on Apple hardware. Every provider is renting you a physical Mac.

Option for macOS buildsFree tierThen…
GitHub Actions — macOS runnerFree minutes count at 10x (2,000 → ~200 real macOS min)~10x Linux, roughly $0.08/min
Xcode Cloud~25 compute-hours/mo (with Developer Program)Paid tiers (~$49.99/mo for 100 hrs, up from there)
Bitrise / CodemagicSmall free tierPer-minute macOS pricing, similar order

Put real numbers on it. A notarized SimpleAppShipper build — archive, sign, notarize, staple, maybe run tests — is realistically 15–25 minutes of macOS time. Call it 20. At hosted macOS rates (~$0.08/min) that's about $1.60 per build. Now your push rate decides the bill:

Builds / monthmacOS minutesApprox. hosted cost / month
10 (occasional)200~$16
40 (a couple/workday)800~$64
100 (active, ~5/workday)2,000~$160

That ~$160/month for an active iOS dev is the number that makes people go looking for another way. There is one, and it's been sitting under your desk the whole time as an idea: stop renting a Mac by the minute. Buy one once.

Buy a Headless Mac Mini and Own Your Builds

This is the "and save" the title promised. A Mac Mini is the cheapest new Apple Silicon machine Apple sells — an M4 base model is around $599 (16 GB / 256 GB, as of 2026; check current specs). Headless means no monitor, no keyboard, no mouse — it lives on a shelf, plugged into power and ethernet, and you reach it over the network. It becomes your private, unmetered, always-on macOS build server.

The mechanism is the self-hosted runner from Ch 21. You register the Mini with GitHub Actions; GitHub keeps giving you the dashboard, the triggers, the green checkmark, the secrets — all free — but the compute runs on your Mini. No per-minute charge. No 10x multiplier. You change one line in your workflow:

jobs:
  build:
    runs-on: [self-hosted, macOS]   # was: macos-latest

…and the exact same pipeline now runs on hardware you own, for the price of electricity.

The break-even math

A Mini is a one-time cost; hosted minutes are forever. So the question is just when does the line cross. Electricity is almost a rounding error — a Mac Mini idles around 4–7 W and peaks ~30–65 W under a build, so even a few build-hours a day is ~$2–4/month.

Build volumeHosted macOS / moMini: months to break evenAfter that
10 builds (light)~$16~46 months~$3/mo electricity
40 builds (steady)~$64~10 months~$3/mo electricity
100 builds (active)~$160~4 months~$3/mo electricity
Xcode Cloud 100-hr tier~$50 flat~13 months~$3/mo electricity

Break-even = $599 ÷ (monthly hosted cost − ~$3 electricity). Light usage never really pays off — if you build a handful of times a month, just use the hosted free tier. The Mini wins decisively the moment you build daily.

Loading diagram…

Figure 2 — For an active iOS dev, a $599 Mini pays for itself in about four months versus hosted macOS minutes, then runs for the cost of a coffee per month. The heavier you build, the faster it crosses.

It's not just a runner — it's a whole Mac you own

The break-even table undersells it, because the Mini isn't a single-purpose runner. The same box does every other expensive-on-rented-hardware job, at no extra cost:

A rented macOS runner vanishes the second your job ends. The Mini is yours — between builds it's still a working Mac.

Setting Up the Mini Headless (No Monitor)

You need a screen and keyboard for the first boot only. After that it's fully remote. Plan on 30–40 minutes once.

  1. First boot (one time, with a monitor): create an admin user, join Wi-Fi/ethernet, install Xcode from the App Store, then in Terminal run sudo xcodebuild -license accept and xcodebuild -runFirstLaunch.
  2. Turn on remote access — System Settings → General → Sharing:
    • Remote Login (SSH) ON → gives you a terminal from your laptop: ssh admin@mini.local.
    • Screen Sharing ON → gives you the full desktop over VNC when you need the GUI (Xcode signing dialogs occasionally do).
  3. Auto-login — System Settings → Users & Groups → Automatically log in as → [your admin user]. A self-hosted runner needs a logged-in GUI session for code-signing and the Simulator to work, so the Mini must reach the desktop on its own after a reboot.
  4. Never sleep — from SSH: sudo pmset -a sleep 0 disablesleep 1. A sleeping Mini is a runner that misses jobs.
  5. Let codesign run unattended — import your Developer ID cert into the login keychain, then authorize it for non-interactive use: security set-key-partition-list -S apple-tool:,apple: -s -k "<password>" ~/Library/Keychains/login.keychain-db. Without this, signing hangs on an invisible password prompt.
  6. Install the runner as a service — Repo (or org) → Settings → Actions → Runners → New self-hosted runner → macOS. Run the ./config.sh … command it gives you, then make it survive reboots:
    ./svc.sh install
    ./svc.sh start
    Now the runner auto-starts on boot and reconnects to GitHub on its own. Unplug the monitor — you're done.
  7. Point your workflow at it: runs-on: [self-hosted, macOS], push, and watch the job land on your Mini in the Actions tab.

The Cheapest Blend

Don't make this all-or-nothing. The cheapest real-world setup mixes the tools, sending each job to whatever runs it for free or near-free:

Loading diagram…

Figure 3 — The blend that costs ~$3/month all-in: free Workers Builds ships the website, the owned Mac Mini does the expensive Apple builds and notarization, and GitHub Actions on Linux runs the optional PR gate within its free tier. Nobody pays the 10x macOS tax, and nobody writes a deploy YAML they don't need.

Note what you don't move: the web deploy stays on Workers Builds because it's already free and zero-maintenance. You only buy hardware to kill the expensive meter — the macOS one. Moving cheap, working things onto your Mini just to "consolidate" is effort without savings.

What simpleappshipper.com Actually Does

Keeping it honest, same as Chs 15 and 21 did: today this project's deploys are run by hand from a laptopnpm run cf:build && npm run cf:deploy for the site, and the notarize-and-staple sequence from CLAUDE.md for the Mac app. Cost: $0, because the developer, deployer, and tester are one person.

The graduation path is exactly this chapter. The day the web deploy should be automatic, it goes to Workers Builds (free, three fields). The day the Mac release build should be automatic — or the day building it by hand on the main laptop gets annoying enough — it goes to a headless Mac Mini self-hosted runner, which builds, notarizes, and uploads while the laptop stays free for actual work. Neither step adds a hosted-CI bill. That's the point: scaling up the automation here doesn't mean scaling up the spend.

Mental Model — Three Sentences

  1. Workers Builds is the free, no-YAML way to ship the web half of a project, but it runs only on Linux — so it physically cannot build the Apple half.
  2. The Apple half is expensive on every cloud because macOS only runs on Apple hardware, and GitHub's hosted macOS runner bills at 10x — which is the one line item worth engineering around.
  3. A ~$599 headless Mac Mini registered as a self-hosted runner turns that metered macOS cost into a flat ~$3/month of electricity, pays for itself in about four months of active building, and doubles as your notarization and upload box forever after.

Try It Yourself (15 Minutes)

  1. Read your own bills. Open GitHub → Settings → Billing → Plans and usage → Usage this month and look at the Actions breakdown by OS. Then open Cloudflare → Manage Account → Billing & Usage. For most readers both are near zero — that's the point, and now you know where to check.
  2. Cap the downside. In GitHub → Settings → Billing → Spending limits → Actions, confirm the limit is $0 (or a number you chose). You just made a surprise bill impossible.
  3. Wire Workers Builds. If you have a Cloudflare Worker, open it → Settings → Build, connect the repo, set build/deploy commands, and push. Watch it deploy with no YAML and no secret.
  4. Do the math for your push rate. Estimate your macOS builds/month × ~20 min × ~$0.08/min. Divide $599 by that (minus ~$3). If the answer is under a year, a Mini will save you money — and if you build daily, it's not close.

Where This Lands in the Series

This closes the operational-cost arc the series has been building since Ch 15. You can now write a CI pipeline (Ch 15), decide whether you even need hosted CI (Ch 21), read the exact pages where both Cloudflare and GitHub keep the bill, cap that bill so it can't surprise you, and — when Apple builds make the cloud meter spin — replace it with a box you own that pays for itself in a season. The whole "ship it reliably without a surprise invoice" story now has real numbers under it.

The macOS-build economics here are the web-series view of a question the Ship iOS series takes on directly: it goes deeper on code signing (the genuinely hard part — harder than the build), Xcode Cloud vs Bitrise vs Codemagic vs fastlane, and the four ways to build and push to TestFlight from your own Mac for $0. If this chapter convinced you the Mini is worth it, that's the chapter on what to run on it.

Before Part 3, one loose thread from this whole arc is worth pulling on its own. We've talked about running the checklist on a server, on a cheap Mini, on your host — but the fastest, cheapest place of all is the laptop already in front of you. Ch 25 makes that the default: what ci:local actually means, the honest pros and cons of running checks on your own machine, every place you can run them (local and online), and the one rule that keeps the two from ever disagreeing. Then Part 3 opens the modern-frontend track — why almost every team in 2026 reaches for a framework like React or Next.js, and what it solves.

Ch 23: Decoding the JargonCh 25: Local CI vs Online CI — What ci:local Means
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