1.1.2 · Dev Platform

How to Build iPhone Apps Without a Mac — EAS + GitHub Codespaces

2026.07.02~7 min

00Overview

Tools

  • Apple Developer ProgramApp Store submission$99/yr
  • EAS buildBuilds on cloud macOSFree
  • GitHub CodespacesOne-time certificate stepFree
  • PC (any)Browser, one time onlyalready owned

01Story

Situation

With the dev loop from 1.1.1, the check-and-fix cycle through Expo Go spins nicely. During development, no build is ever needed.

Complication

Shipping to the App Store is another story. Submitting to Apple means building the app, and building an iPhone app requires a Mac — an unavoidable fact of Apple's ecosystem.

Question

A build needs a Mac. Without owning one, how do you reach the App Store?

02Solution

Criteria

  • Works without owning a Mac (don't buy one, don't borrow one, don't host one)

Answer

# First time only (creating certificates)
PC browser → GitHub → GitHub Codespaces → EAS (build)

# From then on (everything from the iPhone)
iPhone → GitHub → GitHub Actions → EAS (build)
iPhone → GitHub → GitHub Actions → EAS (submit)
        → App Store Connect → TestFlight → iPhone
A Mac is required. It just has to live in the cloud.

EAS (Expo Application Services) builds your iPhone app on macOS machines on their side. All you need at hand is a device that can fire the command — and an iPhone is enough.

Reason

The one-time PC browser session exists because of interactivity. The first build creates the signing credentials for the App Store, and that process asks Y/N questions interactively. The answers are basically all Y, but automating them in GitHub Actions means writing tedious timing-sensitive response code. For an operation that happens a few times a year at most, automation isn't worth it — so the first time is manual.

Even that manual step needs no Mac. Open GitHub Codespaces — the VS Code environment GitHub gives you in a browser — and run the commands from there. The personal free tier (120 core-hours/month) is plenty. In theory a phone browser would work, but a terminal on a phone screen really is too small, so this one step used a PC browser. Again: a PC, not a Mac.

From the second time on, no interaction is needed: run the GitHub Actions workflow from the iPhone and the build rolls. When it succeeds, submit from Actions the same way, and the app lands in App Store Connect and comes down to your device through TestFlight. Right up to the moment of submission, everything completes on the iPhone.

Options

  • Xcode Cloud — Apple's own CI/CD. But the initial setup needs Xcode, i.e., a Mac, which contradicts the "own no Mac" criterion. And since the app is built on EXPO, EAS's affinity settled it.
  • GitHub Actions macOS runners — you can build directly on a macOS runner. But macOS minutes bill at 10× the Linux rate, and the 2,000 free minutes/month evaporate. Never dared.

03Result

Good

Build and submit both live in EAS now, so management is unified. Development in EXPO (1.1.1) continues seamlessly right up to the doorstep of submission — all from a phone. The real find: "you need a Mac," the tallest barrier in iPhone development, turned out not to mean "you need to own a Mac."

Bad

The free tiers come with constraints. As of writing:

ServiceFree tierNotes
EAS build15 iOS builds/mo (+15 Android)Failed builds still count
GitHub Actions2,000 min/moFree plan, private repos

Normal development never hits these ceilings. But debugging native issues with repeated builds eats them fast — and the painful part is that failed builds still consume the count. Three consecutive errors from a config mistake, and a fifth of your month is gone.

Follow-up

Free-tier EAS builds sit at low priority, and the queue can get absurdly long. Waiting is fine — but here's the trap: if Actions sits waiting for the build to finish, that waiting time also drains your 2,000 Actions minutes. Waiting changes nothing about the result, so the right move is to run eas build with --no-wait and end the workflow the moment the trigger fires.

The trade-off is that build and submit no longer chain in one workflow. So it's a two-step routine: confirm the build succeeded on the EAS dashboard, then run submit from Actions. --no-wait works on submit too.

With this, the vertical line of "build it and deliver it" is connected — enough to release. Once released, add the safety layer that separates dev and production delivery — 1.1.3 Safe EAS Update operation — to prevent accidental releases during maintenance. Then the story widens sideways: 1.2 Localization.

I've been triggering GitHub Actions by hand (to save Claude credits), but I'm starting to think it's better to ask Claude to trigger them too — actually, lately I do. When I run Actions by hand, nothing about it stays in Claude's memory, so the next time we talk it sometimes assumes "you haven't released yet, right?" To keep the app's status recorded in Claude's progress file, going through Claude — even at a small credit cost — keeps things from drifting. (Though just telling it "I built it" afterward works too.)

■ Reproduce it yourself

A note for anyone who wants to reproduce this setup. Not meant to be read straight through — the prompt shows a few lines and scrolls.

  1. Enroll in the Apple Developer Program ($99/yr, developer.apple.com/programs)
  2. Create an App ID in App Store Connect (create the app slot → note the ascAppId, a number)
  3. Issue an App Store Connect API Key (Users and Access → Integrations → App Store Connect API). Note the three: .p8 file, Key ID, Issuer ID (the .p8 can't be re-downloaded, so save it)
  4. Add three GitHub Secrets: APPLE_API_KEY (full .p8 text) / APPLE_API_KEY_ID / APPLE_API_KEY_ISSUER_ID

Then paste the following.

I want to build this app for iOS and be able to submit it to App Store Connect.
Use EAS Build to build and EAS Submit to submit, in a setup where from the
second time on I can run it via a GitHub Actions manual trigger (workflow_dispatch).

[What to build]
1. Add the items needed for building to app.json
   - ios.bundleIdentifier (the value I registered in Apple Developer; I'll swap
     it in later, so put a placeholder and note it in a comment)
   - basic items like ios.supportsTablet / requireFullScreen
   - version (start at 1.0.0; note in a comment the policy of bumping it each build)
2. eas.json
   - a production profile
   - submit.production.ios.ascAppId is a placeholder I'll fill in later
3. .github/workflows/eas-build-ios.yml (workflow_dispatch)
   - auto-number buildNumber as YYYYMMDDHHMM and write it into app.json
   - eas build --profile production --platform ios --non-interactive --no-wait
   - pass Apple auth via env from Secrets (APPLE_API_KEY / APPLE_API_KEY_ID /
     APPLE_API_KEY_ISSUER_ID)
4. .github/workflows/eas-submit-ios.yml (workflow_dispatch)
   - write the APPLE_API_KEY secret out to a temp file (.p8) and inject into eas.json
   - eas submit --platform ios --profile production --latest --non-interactive

[Important notes to leave as comments]
- Only the first build needs a manual run in an interactive shell (the certificate
  prompt appears, and CI can't answer it). From the second build on, CI's
  non-interactive mode gets through.
- Bump app.json version every build (to avoid Apple's duplicate-version error)

When you're done, tell me the exact commands to run the first build in Codespaces.

Use the following as a reference for what to tell me.
(these are NOT instructions for Claude Code, but commands I type in the Codespaces terminal)

# 1. Open the repo's Codespace (GitHub Code → Codespaces → Create)
# 2. Install EAS CLI and log in to Expo
npm install -g eas-cli
eas login            # enter your Expo username / password interactively

# 3. Install dependencies
npm install --legacy-peer-deps

# 4. First build (answer "Yes" to the certificate prompts here)
eas build --platform ios --profile production
# → certificates are stored on EAS, so later runs go through non-interactively from CI

When you run the commands yourself, take a phone photo of the PC browser, ask Claude "what do I do?", and it walks you through everything. Mostly you just press Y. You have to create the app ID in App Store Connect (browser); ask Claude "do I have all the fields?" and it tells you.