Home / iOS Build Server

iOS Build Server

An iOS build server turns a commit into a signed, tested build in TestFlight without anyone touching a Mac. Hatmac gives you a dedicated Mac mini or Mac Studio in the cloud to run the whole pipeline.

The pipeline at a glance

  1. Checkout and dependencies: Swift packages, CocoaPods or both, from a warm cache.
  2. Test: unit and UI tests on iOS simulators, in parallel.
  3. Sign: certificates and provisioning profiles installed into a temporary keychain.
  4. Archive and export: an .ipa for App Store Connect.
  5. Upload: to TestFlight with an App Store Connect API key.

Any CI system can drive these steps. Pick one from our macOS CI/CD runner overview, and set up Xcode versions and caching with the Xcode build server guide first.

Mac mini M6

Most popular

24GB · 512GB SSD

More memory and storage for larger projects and caches.

from $179/mo

  • Monthly$209/mo
  • 6 months$199/mo
  • 12 months$179/mo
Order Mac mini M6

Mac mini M5 Pro

24GB · 512GB SSD

Pro-class CPU and GPU for heavy builds and small models.

from $229/mo

  • 6 months$249/mo
  • 12 months$229/mo
Order Mac mini M5 Pro

Mac Studio M5 Max

36GB · 512GB SSD

High GPU throughput for ML, rendering and parallel CI.

from $679/mo

  • 6 months$699/mo
  • 12 months$679/mo
Order Mac Studio M5 Max

Prices in USD per month for each machine's starting configuration. More memory, storage or several machines? Ask us.

Code signing on an iOS build server

Signing is where most headless iOS builds fail. You need two things on the server: a distribution certificate with its private key, and a provisioning profile that matches the bundle ID, team and certificate.

  • Certificates live in a keychain. On a build server, use a dedicated keychain that the job unlocks, not the login keychain.
  • Provisioning profiles are files Xcode reads from the user's Library. They expire and must be renewed when devices or capabilities change.
  • Automatic signing in Xcode can work on CI, but manual signing with fixed profiles is more predictable.

Share signing with fastlane match

fastlane match stores encrypted certificates and profiles in a private git repository or cloud bucket, so every developer and every build server uses the same identity. Create them once from a developer machine, then use read-only mode on the server so CI never creates or revokes certificates.

fastlane match init                    # once, choose a private git repo
fastlane match development
fastlane match appstore
# on the build server, never create or revoke:
fastlane match appstore --readonly

Keep MATCH_PASSWORD and the repository access key in your CI secret store.

Upload to TestFlight with an API key

Use an App Store Connect API key instead of an Apple Account password. It avoids two-factor prompts and can be revoked on its own. Create one in App Store Connect under Users and Access → Integrations, with the App Manager role, and save the key ID, issuer ID and .p8 file as CI secrets.

lane :beta do
  setup_ci                             # temporary keychain for this run
  app_store_connect_api_key(
    key_id: ENV["ASC_KEY_ID"],
    issuer_id: ENV["ASC_ISSUER_ID"],
    key_content: ENV["ASC_KEY_P8_BASE64"],
    is_key_content_base64: true
  )
  match(type: "appstore", readonly: true)
  increment_build_number(build_number: ENV["BUILD_NUMBER"])
  build_app(scheme: "MyApp", export_method: "app-store")
  upload_to_testflight(skip_waiting_for_build_processing: true)
end

setup_ci creates a temporary keychain when it detects a CI environment, which avoids keychain prompts. skip_waiting_for_build_processing frees the runner as soon as the upload finishes, instead of waiting for Apple's processing.

Testing on simulators

Build once, then test against several simulators. Splitting build-for-testing and test-without-building lets you rerun tests without recompiling.

# device names: see xcrun simctl list devices available
xcodebuild build-for-testing -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 17' \
  -derivedDataPath "$HOME/ci-cache/DerivedData/MyApp"

xcodebuild test-without-building -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 17' \
  -destination 'platform=iOS Simulator,name=iPad Pro 11-inch (M4)' \
  -derivedDataPath "$HOME/ci-cache/DerivedData/MyApp" \
  -parallel-testing-enabled YES -parallel-testing-worker-count 3 \
  -resultBundlePath build/Tests.xcresult
  • Parallel testing runs test classes across several simulator clones. Each clone uses memory, so the worker count you can sustain depends on the plan. Start at two or three on 24GB and measure.
  • Several destinations run iPhone and iPad suites in one command.
  • Result bundles (.xcresult) keep logs, screenshots and coverage. Archive them as CI artifacts.
  • Run xcrun simctl shutdown all and xcrun simctl erase all between runs if tests leave state behind.

Choosing a plan for iOS builds

Team and appSuggested Mac
One app, small team, unit testsMac mini M6, 24GB / 512GB
Larger app, UI tests on several simulatorsMac mini M5 Pro, 24GB / 512GB
Many targets or apps, heavy parallel UI testingMac Studio M5 Max, 36GB / 512GB
Several teams or many concurrent jobsSeveral machines as a build farm

For a complete example with GitHub Actions, read iOS CI/CD on a cloud Mac mini. If you run Actions, the GitHub Actions Mac runner page covers registration and labels.

FAQ

Can an iOS build server install apps on real iPhones?

Not directly. Devices need to be paired with the Mac over USB or the local network, which a remote Mac cannot reach. Use simulators for automated tests and TestFlight to put builds on real devices.

Do I need an Apple Developer Program membership?

Yes, to sign for distribution and upload to TestFlight or the App Store. Simulator builds and tests do not need it.

Is fastlane required?

No. You can sign, archive and upload with xcodebuild and Apple's tools alone. fastlane match and its upload actions just make signing and TestFlight easier to repeat.

Is my signing identity safe on a rented Mac?

Each Hatmac machine is a dedicated physical Mac assigned only to you. Keep certificates in a separate keychain, store passwords as CI secrets, and use match in read-only mode.