If you want to put an app on the App Store, Apple requires you to build and sign it with Xcode, and Xcode only runs on macOS. That's true no matter what you built the app with — Swift, React Native, Capacitor, Flutter, whatever. At some point, something running on a Mac has to sign the thing.
Not owning a Mac is a common enough problem that there are a few genuine ways around it, each with its own cost. Here's what I found when I hit this wall building an iOS app of my own, and what each option actually costs versus what it's usually advertised as.
Option 1: Buy a Mac
The straightforward option. A Mac mini starts around $799 (Apple raised the entry price in 2026), which is a one-time cost — no recurring bill, and it's hardware you own outright and can use for other things. If you're going to be doing iOS development regularly, long-term, this is genuinely the simplest answer and probably the right one.
Where it doesn't make sense: a side project, a one-off app, or testing whether an idea is worth pursuing before you've made any money from it. Spending $799 to find out if your app idea has legs is a genuine barrier, and it's the reason a lot of iOS side projects never ship.
Option 2: Rent a cloud Mac
Services like MacStadium, MacinCloud, and similar exist specifically to rent you remote access to an actual Mac by the hour or month. Typical pricing runs anywhere from about $20/month for a shared, lower-spec machine up to $100+/month for dedicated, faster hardware.
This solves the ownership problem but trades it for a recurring bill, and most of these services are built around interactive use — you remote into a desktop and click around, same as if you owned the machine. For something you might only need for a few minutes every time you cut a release, paying by the month is a lot of unused capacity.
Option 3: GitHub Actions' free macOS runners
The option that actually got me unblocked. GitHub Actions — GitHub's built-in CI/CD system — provides macOS build machines, and on public repositories, macOS runner minutes are free. They're intended for running test suites before a merge. Nothing about a macOS runner requires that it only run tests, though — it's an actual macOS machine with Xcode's command-line tools available, and it can run the exact same xcodebuild commands that sign, archive, and export an app that Xcode's GUI runs under the hood. (More detail on how the free tier actually works in this post.)
At a high level, the pipeline looks like this:
$ git push origin main → GitHub spins up a free macOS build machine → workflow checks out your project → signing certificate + provisioning profile are installed from secrets you set once → xcodebuild archives the app and exports a signed .ipa → the .ipa uploads straight to App Store Connect / TestFlight
No Mac touched at any point. You push code, the machine that briefly exists to build it goes away when it's done, and the signed build shows up in TestFlight.
What it costs
| Approach | Cost | Ongoing? |
|---|---|---|
| Buy a Mac | ~$799+ | No — one-time |
| Rent a cloud Mac | ~$20–100+/mo | Yes — for as long as you develop |
| GitHub Actions (public repo) | $0 for macOS build minutes | No — free tier is ongoing, not time-limited |
One thing to plan around: this $0 figure is specifically for public repos. Private repos work exactly the same way, pipeline and all — you just get a monthly allowance of free minutes instead of an unlimited amount, and pay per minute past it (macOS minutes run at roughly 10x the cost of standard Linux minutes). That's usually still cheaper than renting a Mac, just not literally free. Either way, your signing certificates and credentials sit in encrypted GitHub secrets, never exposed in the repo or build logs — repo visibility only affects CI minute pricing, nothing else. You also still owe Apple's $99/year Apple Developer Program fee regardless of which build option you use — none of this replaces that, it only removes the Mac requirement. (Full cost breakdown in this post.)
Where this approach actually gets hard
The build pipeline itself is a fairly short workflow file. The part that actually eats time is code signing, because it's fussy in ways that aren't obvious until you've hit them:
- Certificate/profile mismatches. A provisioning profile is generated against one specific signing certificate. If you regenerate the certificate in Apple's portal without regenerating every profile that references it, builds start failing with signing errors that don't clearly say why. (Walked through in detail here.)
- Exact-name matching. Provisioning profile names and bundle IDs have to match character-for-character (case and whitespace included) between Apple's portal and your CI configuration, or codesigning fails.
- Silent TestFlight failures. This one is sneaky: if you reupload a build using the same marketing version and build number as a previous upload, App Store Connect silently rejects it — but the upload tool reports success either way, so your CI log shows green while the build never actually appears in TestFlight. The fix is making sure your build number changes on every single run (GitHub's own per-workflow run counter works well for this). (Full walkthrough here.)
- Export Compliance and App Privacy. Not CI problems, but they'll block a TestFlight install or App Store submission if you haven't answered them, and they're easy to miss the first time through App Store Connect.
None of this is a sign the approach doesn't work — it's the same signing complexity every iOS build has, Xcode's GUI just hides more of it behind clicks. Once the certificates, profiles, and workflow are set up correctly once, every build after that is just git push.
Which option is actually right for you
If you're going to be doing iOS work regularly for years, buying a Mac is genuinely the simplest long-term answer. If you need occasional interactive access to a macOS desktop — for reasons beyond just building, like testing something Simulator-only — a rented cloud Mac covers that in a way CI can't. But if what you actually need is just: build this, sign it, get it into TestFlight, on a public repo — GitHub Actions' free macOS minutes get you there without spending anything on hardware or hosting.
I wrote up the whole setup
I went through all of this shipping Citolex, a full App Store app with native Swift plugins, entirely through this pipeline. I packaged the GitHub Actions workflow, the signing script, and a setup guide that covers every one of the pitfalls above (plus the fixes) as a one-time template — no subscription.
See what's included — $99