The web series asked "Do you actually need GitHub Actions?" and landed on: for a solo project, no — run the checklist locally for $0. iOS reaches the same conclusion but by a different road, because iOS has one hard constraint that changes all the math:
Xcode only runs on macOS. So your CI can't use the cheap Linux machines everyone else uses — it's forced onto Apple hardware, which every provider charges a premium for.
This chapter is the iOS-shaped version of that decision. By the end you'll know exactly what an iOS pipeline costs, why code signing (not the build) is the part that actually hurts, what Xcode Cloud / Bitrise / Codemagic / fastlane each do, and four ways to build and push to TestFlight from your own Mac without paying a cent.
The One Constraint That Changes Everything
A web build runs on Linux. Linux runners are cheap, fast, and abundant — which is why web CI is basically always free (see the web chapter).
An iOS build needs Xcode, and Xcode needs macOS, and macOS only runs (legally) on Apple hardware. Data centers full of Mac minis are expensive to operate, so every CI provider charges 5–10x more for a Mac runner than a Linux one. On GitHub Actions it's a literal 10x multiplier:
| Runner | Multiplier | A 10-min build counts as… |
|---|---|---|
| Linux (web builds) | 1x | 10 minutes |
| macOS (iOS builds) | 10x | 100 minutes |
GitHub's Free plan includes 2,000 Linux-equivalent minutes/month. Run them on macOS and that's only ~200 real Mac minutes a month. An Xcode archive (compile + sign + package) easily takes 5–15 minutes, so you get roughly 15–30 builds a month before you start paying — and Mac minutes are the priciest per-minute too.
The Part That Actually Hurts: Code Signing
Here's what nobody warns you about. The build on a CI server is easy — it's one command:
xcodebuild -scheme "MyApp" -archivePath build/MyApp.xcarchive archiveThe hard part is that Apple won't let that archive run on a device, or upload to TestFlight, unless it's code-signed with your identity. And signing needs three secrets present on the runner:
| Thing | What it is | Where it comes from |
|---|---|---|
| Signing certificate | Proves the build came from you (a .p12 with a private key) | Apple Developer account → Certificates |
| Provisioning profile | Links your cert + app ID + allowed devices/capabilities | Apple Developer account → Profiles |
| App Store Connect API key | A .p8 file that lets tools upload builds without your password | App Store Connect → Users and Access → Integrations |
On your own Mac, Xcode hides all of this — "Automatically manage signing" just works because your certs live in your Keychain. On a fresh CI runner, none of it exists, and you have to inject all three securely (without committing them to git). That plumbing — not the build — is 90% of the effort and frustration of iOS CI. It's also why iOS-specific tools exist: they mostly exist to make signing bearable.
Figure 1 — In an iOS pipeline the compile step is trivial; getting your signing identity safely onto a machine you don't own is the work. The next chapter in this series is a full code-signing walkthrough.
What iOS CI Actually Buys You
Same core value as any CI (run the checklist on a clean machine), plus things that are specifically painful to do by hand on iOS:
- Run your test suite (
xcodebuild test) on a clean simulator every push — catch regressions before they ship. - Push-button TestFlight betas — build → sign → upload → notify testers, with zero manual Xcode clicking. This is the #1 reason indie iOS devs adopt CI.
- Screenshot automation — generate every App Store screenshot, every device size, every language, reproducibly (via
fastlane snapshot). - A pinned Xcode version — no more "works with my Xcode 16.2 but the App Store build used 16.4." The runner uses exactly one.
If none of those hurt yet, you don't need CI yet. If "archive, wait, upload to TestFlight, wait, click through App Store Connect" every release is grinding you down — that's the pain CI removes.
So… Do You Need It?
| Your situation | Need hosted iOS CI? |
|---|---|
| Solo, shipping a release every few weeks | No. Archive + upload from Xcode. It's a few clicks; the 10x cloud tax isn't worth it. |
| Solo, but TestFlight every other day | Maybe — automate it locally with fastlane (free) before paying for cloud. |
2+ devs, shared main | Yes. Run tests on every PR so nobody breaks the build. |
| You want betas to ship while you sleep | Yes — that's exactly what hosted CI + TestFlight automation is for. |
| You have a spare Mac sitting around | Use it as a self-hosted runner — hosted-style CI, $0 compute. (Below.) |
The honest default for an indie iOS dev: you don't need hosted CI to ship. You need the build to happen and the upload to happen — both of which your own Mac does for free. CI earns its cost when releases get frequent enough that doing it by hand is the bottleneck, or when more than one person can break the build.
The iOS-Specific Tools (and What Each One Is For)
Generic CI (raw GitHub Actions YAML) can build iOS, but the Apple-specific friction — Xcode versions, simulators, signing — is why these exist:
| Tool | What it actually is | Why iOS devs reach for it |
|---|---|---|
| Xcode Cloud | Apple's own CI, built into Xcode + App Store Connect | Manages signing for you automatically — the killer feature. Free tier in compute hours, not penalized by a 10x multiplier. |
| Bitrise | Hosted mobile-first CI | Pre-installed Xcode, visual workflow builder, deep iOS/Android focus. Free tier for small projects. |
| Codemagic | Hosted mobile-first CI | Generous free macOS (M-series) build minutes, fast, popular with Flutter + native iOS. |
| fastlane | Not CI — an open-source automation toolkit | The glue. Runs the same build/sign/upload/screenshot steps inside any CI or on your laptop. Free. |
| GitHub Actions | General CI with macOS runners | Fine for iOS if you already live on GitHub — just mind the 10x macOS minutes. |
Xcode Cloud's Free Tier Is Quietly the Best Deal
One detail worth calling out: Xcode Cloud bills in compute hours, not 10x-penalized minutes, and includes roughly 25 compute hours/month free (always confirm current limits). That's ~1,500 minutes of real Mac build time — far more generous for iOS than GitHub's 2,000 Linux-minutes-becomes-200-Mac-minutes math. Plus it handles code signing automatically, which erases the hardest part of this chapter.
For many indie iOS devs the cheapest hosted option is simply: turn on Xcode Cloud, use the free tier, let Apple manage signing. If you're going to pay anyone for iOS CI, Apple's own is often both the cheapest and the least painful.
The $0 Path: Ship From Your Own Mac
This is the direct answer to "can I do it locally without paying?" — and for iOS it's even more compelling, because you already own a Mac (you can't build iOS without one). Four ways, simplest first.
1. Just archive and upload from Xcode
The lowest-tech release: Product → Archive in Xcode, then Distribute App → TestFlight/App Store. Xcode handles signing from your Keychain and uploads directly. Zero config, zero cost. For a solo dev shipping every few weeks, this is genuinely fine — don't let anyone CI-shame you out of it.
2. Automate the release locally with fastlane
When the clicking gets old, script it — still on your Mac, still free:
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Build and push a beta to TestFlight"
lane :beta do
build_app(scheme: "MyApp") # wraps xcodebuild
upload_to_testflight # wraps the ASC upload
end
endfastlane beta # one command: build, sign, upload to TestFlightThat's a complete TestFlight pipeline running on your laptop for $0. The same Fastfile works unchanged if you ever move to hosted CI — you've lost nothing by starting local.
3. Build straight from the command line
No Xcode UI, no fastlane — just the tools that ship with Xcode:
xcodebuild -scheme "MyApp" -archivePath build/MyApp.xcarchive archive
xcodebuild -exportArchive -archivePath build/MyApp.xcarchive \
-exportPath build/export -exportOptionsPlist ExportOptions.plist
xcrun altool --upload-app -f build/export/MyApp.ipa \
--apiKey XXXX --apiIssuer YYYY # uses your .p8 App Store Connect keyThis is exactly what a CI server would run — you're just running it on the Mac in front of you. (It's also, almost line-for-line, what SimpleAppShipper's own Binary Upload feature wraps for you.)
4. Self-hosted Mac runner — hosted-style CI, $0 compute
Here's the one that makes automated, push-triggered iOS CI free even with the 10x problem. A self-hosted runner registers your own Mac with GitHub Actions: you still get the dashboard, the green checkmark, the per-PR status checks — but builds run on your hardware, so there are no Mac minutes billed and no 10x multiplier.
jobs:
build:
runs-on: self-hosted # your Mac, not GitHub's macos-latest
steps:
- uses: actions/checkout@v4
- run: fastlane betaA spare Mac mini on a shelf is the ideal iOS CI machine — and it's the same Mac mini this project already uses for its capture pipeline. It already has your Xcode and signing identity, so there's barely any setup.
The Price Comparison
"Cost after free" assumes a small project; iOS pricing pages move, so confirm before budgeting.
| Option | Free tier | Cost after free | Signing | Best for |
|---|---|---|---|---|
| Archive + upload from Xcode | Free forever | — | Automatic (Keychain) | Solo, occasional releases |
| fastlane on your Mac | Free forever | — | You set up once (match) | Solo, frequent TestFlight |
| Self-hosted Mac runner | Free (your hardware) | Your electricity | Reuses your Mac's identity | Automated CI, $0, no 10x tax |
| Xcode Cloud | ~25 compute hrs/mo | Tiered (~$15/mo for more) | Automatic | Cheapest + easiest hosted |
| GitHub Actions (macOS) | ~200 Mac min/mo | ~10x Linux rate | Manual (you inject secrets) | Teams already on GitHub |
| Bitrise / Codemagic | Small free tier | Usage-based | Helpers provided | Mobile-first teams |
Picking the Cheapest Thing That Fits
Figure 2 — Like the web version, almost every branch is free. The cloud option only wins when you have no spare Mac and want signing handled for you.
What SimpleAppShipper Actually Does
To stay honest (same as the web chapter): this project ships its Mac app with a fully local pipeline — xcodebuild archive → export with Developer ID signing → notarytool → stapler → DMG → upload to R2. No hosted CI, no cloud Mac minutes. It's a Mac app distributed directly (not through the App Store), so the pipeline is even more local than a typical iOS-to-TestFlight flow.
The app also contains the local-shipping idea as a feature: its Binary Upload module wraps xcrun altool --upload-app (writing your .p8 to a temp dir and setting API_PRIVATE_KEYS_DIR), and its CI Builds panel can ingest webhooks from Bitrise, Codemagic, GitHub Actions, or Xcode Cloud if you later add hosted CI. The design assumption is exactly this chapter's conclusion: start local and free; reach for hosted CI only when the release cadence demands it.
Mental Model — Three Sentences
- iOS CI is "expensive" only because Xcode forces you onto premium Apple hardware — so the way to make it free is to use the Mac you already own (locally, or as a self-hosted runner that dodges the 10x tax).
- The build is the easy part; getting your signing identity (cert + provisioning profile +
.p8key) safely onto a machine is the real work — which is why fastlane and Xcode Cloud mostly exist to manage signing. - A solo dev never needs to pay: archive from Xcode or script
fastlane betaon your Mac, and graduate to Xcode Cloud's free tier or a self-hosted runner only when releases get frequent or a teammate joins.
Try It Yourself (15 Minutes)
- In any iOS project, run
xcodebuild -scheme "YourScheme" -showBuildSettings | grep -i signand read what signing identity Xcode is using. That's the secret a CI runner would need. - Install fastlane (
brew install fastlane), runfastlane initin your project, and read the generatedFastfile. You now have a local automation harness — free. - Create a
betalane like the one above (don't run it against a real app yet if you're not ready to upload). Notice it's justbuild_app+upload_to_testflight. - Open App Store Connect → Users and Access → Integrations and look at where API (
.p8) keys are created. That single file is what lets any tool upload without your password. - (Optional) If you have a spare Mac, open a GitHub repo → Settings → Actions → Runners → New self-hosted runner and read the setup script. That's $0 automated iOS CI.
Where This Lands in the Series
This is Chapter 1 of the Ship iOS Apps series — the iOS sibling of the web operational track. We started with the money question on purpose, because it determines whether you even want a server in the loop.
Next chapters go deep on the parts this one only named: code signing without tears (certificates, provisioning profiles, and fastlane match), a complete TestFlight automation lane, App Store screenshot automation, and surviving App Store review. Every one of them runs the same locally-or-hosted way — so whatever you learn, you can do for free first.
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