
Xcode Cloud's 25 free compute hours disappear fast on a team with several active branches. Apple's published paid tiers step up to US$49.99/month for 100 hours, US$99.99/month for 250 hours, US$399.99/month for 1,000 hours and US$3,999.99/month for 10,000 hours — see the Xcode Cloud subscription page. A team running ten 15-minute builds a day consumes the 100-hour tier in about ten working days. A self-hosted Mac on Figment is a flat monthly fee rather than a metered build-minute clock: the $29 virtual Intel macOS runner handles Intel-compatible Xcode workloads (Xcode 26 or earlier), and M-series starts at $95/month with the exact chip, RAM and disk confirmed in the quote. If you are already past 100 Xcode Cloud hours a month, price the Figment option against your current tier using the worksheet below, then follow the mapping table to cut over without losing a release cycle. Primary references used throughout: Apple's Xcode Cloud documentation and Configuring your first Xcode Cloud workflow.
Every Xcode Cloud workflow has the same shape, and replicating it requires naming each piece:
.xctestplan against simulator or device destinations.Workflows are configured in Xcode's Report navigator or in App Store Connect; there is no YAML file on disk. Migration is deliberate mapping, not a file copy.
Mark each row done when the self-hosted command produces output that matches the Xcode Cloud build.
| Xcode Cloud action | Self-hosted equivalent | Notes |
|---|---|---|
| Start: branch push | CI webhook on push | GitHub Actions, Buildkite, GitLab CI, Jenkins |
| Start: pull request | CI pull_request trigger | Scope with -only-testing: for PR test subsets |
| Start: tag | CI tag trigger | Use for release builds |
| Start: schedule | CI cron trigger | Nightly builds, regression sweeps |
| Environment: Xcode version | DEVELOPER_DIR pointing at the installed Xcode bundle | See the sibling guide below on multiple Xcode versions |
| Build | xcodebuild build -scheme … -destination … or fastlane gym | gym wraps archive and export |
| Test (unit) | xcodebuild test -scheme … -testPlan … | Pass the committed .xctestplan path |
| Test (UI) | xcodebuild test -scheme … -destination "platform=iOS Simulator,…" | Destination must match an installed simulator |
| Archive | xcodebuild archive -scheme … -archivePath … | Required before export and upload |
| Export IPA | xcodebuild -exportArchive -archivePath … -exportOptionsPlist … or gym | Keep exportOptionsPlist in source control |
| Post: TestFlight | fastlane upload_to_testflight | Authenticate with an App Store Connect API key |
| Post: App Store | fastlane upload_to_app_store | Metadata upload is optional |
| Post: Slack | fastlane slack with webhook URL | Store SLACK_URL in CI secrets |
| Post: email | CI notification plugin on the runner | Most CI platforms ship a built-in email step |
Hardware note: Apple silicon is required for Xcode 27, visionOS simulators and ARM-only execution. Intel-compatible targets built with Xcode 26 or earlier run on the $29 virtual Intel macOS runner; see Apple's porting guide for the exact boundary. Choose M-series when your matrix includes Xcode 27, ARM execution or GPU-heavy tests.
Xcode Cloud reads the .xctestplan files you pick in its UI. xcodebuild reads the same files:
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme "$SCHEME" \
-testPlan "$TEST_PLAN" \
-destination "platform=iOS Simulator,name=iPhone 16,OS=18.0" \
-resultBundlePath "$RUNNER_TEMP/TestResults.xcresult"
$SCHEME and $TEST_PLAN are your project's real values — do not hard-code a scheme name that does not exist in the workspace. If the test plan is not yet in source control, export it from Xcode (Product → Test Plan → Export) and commit it before migration. Confirm destinations with:
xcrun simctl list devices available
A mismatched destination string is the most common reason a parity run diverges from Xcode Cloud.
The two post-actions that matter for most teams are TestFlight upload and Slack notification.
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Archive, upload to TestFlight, notify Slack"
lane :beta do
gym(
scheme: ENV.fetch("SCHEME"),
workspace: ENV.fetch("WORKSPACE"),
export_method: "app-store",
output_directory: ENV["RUNNER_TEMP"] || "./build",
output_name: "MyApp.ipa"
)
upload_to_testflight(
api_key_path: ENV.fetch("ASC_API_KEY_JSON"),
skip_waiting_for_build_processing: true
)
slack(
message: "TestFlight build uploaded",
channel: "#ios-releases",
slack_url: ENV.fetch("SLACK_WEBHOOK_URL")
)
end
end
The api_key_path value points at an App Store Connect API key JSON that Fastlane reads at runtime; generate the .p8 in App Store Connect under Users and Access → Integrations, then assemble the JSON per the Fastlane App Store Connect API docs. Keep the .p8 and the JSON in your CI secret manager — never commit them, and never echo them into shell history. Signing certificates should be stored as encrypted .p12 files with the private key; provisioning profiles are regular .mobileprovision files copied into ~/Library/MobileDevice/Provisioning Profiles/. Neither is exported from Keychain by a CI step.
Do not disable Xcode Cloud until you have confirmed parity on at least three real builds.
develop. Diff the artifacts and the .xcresult summaries.Parallel operation costs both Xcode Cloud hours and the Figment monthly fee for one or two weeks. That overlap is cheaper than a failed release caused by an undiscovered gap.
Use your own numbers; the example below is a moderate-volume team.
| Metric | Xcode Cloud 250-hr tier | Figment M-series entry |
|---|---|---|
| Monthly cost | $99.99 (Apple) | from $95 (entry price; exact chip/RAM/disk set in the quote) |
| Billing model | Metered compute hours, unused hours expire | Flat monthly fee, not metered |
| Per-build cost at 400 builds | $0.25 ($99.99 ÷ 400) | $0.24 ($95.00 ÷ 400) |
| Next step up | $399.99 for 1,000 hours | Confirm larger spec in the quote |
The entry-price per-build numbers are close, but they do not represent equivalent hardware. Xcode Cloud is Apple's shared managed infrastructure; the Figment entry is a single-tenant Mac whose spec is confirmed at quote time. The practical advantage of self-hosting appears at the next step up: once build volume exceeds the 250-hour tier, Xcode Cloud jumps to $399.99/month for 1,000 hours, while a Figment M-series stays on a flat monthly fee for the spec you request. Smaller teams under 100 compute hours a month may find Xcode Cloud's $49.99 tier cheaper than any dedicated Mac; in that range the $29 virtual Intel macOS runner is the realistic Figment comparison for Intel-compatible targets, and Xcode Cloud remains the simpler option for ARM-only builds.
Once a self-hosted release has shipped cleanly, open App Store Connect → Xcode Cloud → Workflows, select the workflow and disable or delete it. The same workflow is also reachable from Xcode's Report navigator; UI labels shift between Xcode point releases, so follow Apple's current Xcode Cloud documentation rather than screenshots. After one or two billing cycles with no Xcode Cloud usage, lower or cancel the subscription tier.
.xctestplan files committed to source controlexportOptionsPlist or gym export_method set for app-store export.p12 and .mobileprovision files installed on the runnerxcrun simctl list devices availableReady to size the Mac? Review the plans at /mac-hosting and begin at /mac-hosting/start.
Related guides: