Tutorials Ship iOS Apps Series Chapter 1

Do You Need CI for an iOS App? The macOS 10x Tax, Code Signing, and Shipping for $0

Ship iOSChapter 1 of the Ship iOS Apps Series26 minMay 28, 2026Beginner

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:

RunnerMultiplierA 10-min build counts as…
Linux (web builds)1x10 minutes
macOS (iOS builds)10x100 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 archive

The 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:

ThingWhat it isWhere it comes from
Signing certificateProves the build came from you (a .p12 with a private key)Apple Developer account → Certificates
Provisioning profileLinks your cert + app ID + allowed devices/capabilitiesApple Developer account → Profiles
App Store Connect API keyA .p8 file that lets tools upload builds without your passwordApp 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.

Loading diagram…

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:

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 situationNeed hosted iOS CI?
Solo, shipping a release every few weeksNo. Archive + upload from Xcode. It's a few clicks; the 10x cloud tax isn't worth it.
Solo, but TestFlight every other dayMaybe — automate it locally with fastlane (free) before paying for cloud.
2+ devs, shared mainYes. Run tests on every PR so nobody breaks the build.
You want betas to ship while you sleepYes — that's exactly what hosted CI + TestFlight automation is for.
You have a spare Mac sitting aroundUse 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:

ToolWhat it actually isWhy iOS devs reach for it
Xcode CloudApple's own CI, built into Xcode + App Store ConnectManages signing for you automatically — the killer feature. Free tier in compute hours, not penalized by a 10x multiplier.
BitriseHosted mobile-first CIPre-installed Xcode, visual workflow builder, deep iOS/Android focus. Free tier for small projects.
CodemagicHosted mobile-first CIGenerous free macOS (M-series) build minutes, fast, popular with Flutter + native iOS.
fastlaneNot CI — an open-source automation toolkitThe glue. Runs the same build/sign/upload/screenshot steps inside any CI or on your laptop. Free.
GitHub ActionsGeneral CI with macOS runnersFine 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
end
fastlane beta     # one command: build, sign, upload to TestFlight

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

This 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 beta

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

OptionFree tierCost after freeSigningBest for
Archive + upload from XcodeFree foreverAutomatic (Keychain)Solo, occasional releases
fastlane on your MacFree foreverYou set up once (match)Solo, frequent TestFlight
Self-hosted Mac runnerFree (your hardware)Your electricityReuses your Mac's identityAutomated CI, $0, no 10x tax
Xcode Cloud~25 compute hrs/moTiered (~$15/mo for more)AutomaticCheapest + easiest hosted
GitHub Actions (macOS)~200 Mac min/mo~10x Linux rateManual (you inject secrets)Teams already on GitHub
Bitrise / CodemagicSmall free tierUsage-basedHelpers providedMobile-first teams

Picking the Cheapest Thing That Fits

Loading diagram…

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 pipelinexcodebuild archive → export with Developer ID signing → notarytoolstapler → 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

  1. 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).
  2. The build is the easy part; getting your signing identity (cert + provisioning profile + .p8 key) safely onto a machine is the real work — which is why fastlane and Xcode Cloud mostly exist to manage signing.
  3. A solo dev never needs to pay: archive from Xcode or script fastlane beta on 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)

  1. In any iOS project, run xcodebuild -scheme "YourScheme" -showBuildSettings | grep -i sign and read what signing identity Xcode is using. That's the secret a CI runner would need.
  2. Install fastlane (brew install fastlane), run fastlane init in your project, and read the generated Fastfile. You now have a local automation harness — free.
  3. Create a beta lane like the one above (don't run it against a real app yet if you're not ready to upload). Notice it's just build_app + upload_to_testflight.
  4. 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.
  5. (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.

← Series OverviewComing Soon →
SwiftUltimate Swift SeriesSwift fundamentals for app developers who want to understand the language behind real iOS and macOS apps.SwiftUIUltimate SwiftUI SeriesSwiftUI tutorials for building native app screens, layouts, navigation, and state-driven interfaces.DeliveryModern Delivery PipelineCI/CD, review, runner, and deploy workflows for teams shipping apps and websites safely.

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