
Run the arm64 CI build on a native Apple silicon host and gate the job with lipo plus a Rosetta check. Request a Figment M-series Mac — plans start at $95/month and the exact chip, RAM and disk are confirmed when Figment returns a configuration quote. The $29 virtual Intel macOS environment can cross-compile an arm64 Mach-O with a compatible Intel Xcode (Xcode 26 or earlier), but it cannot execute arm64 binaries, so it cannot validate them end to end in the same job.
If your macOS CI sits on an Intel runner and you ship a command-line tool or framework, an x86_64 binary will launch happily on your users' Apple silicon Macs — under Rosetta. The job reports green, the users get translated code with higher launch latency and no arm64-specific instructions. This guide closes that gap: force the right build settings, compile natively on an M-series Mac, and add a post-build guard that fails the job on the spot when the arm64 slice is missing.
Rosetta 2 is transparent by design. An x86_64 binary launched on Apple silicon is translated on the fly, prints output, and exits zero. Nothing in the default xcodebuild output says a translation happened. If your runner is an Intel Mac — or an M-series Mac invoked through arch -x86_64 — the resulting binary carries only an x86_64 slice. Per Apple's Building a universal macOS binary note, you can build a universal binary from either platform, but you cannot run or debug the arm64 slice on an Intel-based Mac. That is the core reason a verification step matters even when the compile succeeds.
Two Xcode build settings decide which slices land in the output.
| Setting | Value | Effect |
|---|---|---|
ARCHS | arm64 | Build only the arm64 slice |
ARCHS | arm64 x86_64 | Build a universal binary in one pass |
ONLY_ACTIVE_ARCH | NO | Respect ARCHS regardless of host arch |
ONLY_ACTIVE_ARCH | YES | Build only the native arch of the host |
For CI set ONLY_ACTIVE_ARCH=NO so the output is not silently reduced to whatever the host happens to be. Pass both on the command line and keep your own scheme name:
xcodebuild \
-scheme "$SCHEME" \
-configuration Release \
ARCHS=arm64 \
ONLY_ACTIVE_ARCH=NO \
build
Universal binaries are covered separately in the Universal Binary CI Pipeline guide.
Swift Package Manager accepts --arch to pick the destination slice. Without it, swift build targets the host architecture — fine on an M-series Mac, wrong on Intel.
# Release build, arm64 slice only
swift build -c release --arch arm64
# Print the product path (do not hard-code it)
swift build -c release --arch arm64 --show-bin-path
On an Apple silicon runner, --arch arm64 is the native architecture, so SPM compiles without cross-compilation overhead. Refer to the Swift Package Manager documentation for the full option list and version differences.
Figment lists M-series Mac plans from $95/month; the exact chip generation, RAM and disk are confirmed in the quote and physical capacity is request-based. If you want Figment to run the pipeline for you, Managed Build Mac is $249/month and includes one M-series Mac, CI setup, backups, monitoring and 30 minutes of monthly support. Managed Mac Team is $999/month and includes two M-series Macs, two CI runners, team setup, backups, monitoring and one hour of monthly support. The $29 virtual Intel macOS plan (2 vCPU, 8 GiB RAM, 80 GiB disk; Intel on Linux/KVM, no GPU or audio) is useful for compiling and signing supported Intel toolchains, but it is not an Apple silicon Mac and cannot execute arm64 output.
Entry-price snapshot for a developer who only needs one Apple silicon runner. These are entry prices on each vendor's own pricing page, not equal hardware; confirm chip and RAM in the quote.
| Vendor | Headline | Notes & Source |
|---|---|---|
| Figment M-series | from $95/month | Chip/RAM/disk confirmed in the quote; request-based |
| CloudMac monthly | $99.99/month | M4, 16 GB RAM, 256 GB SSD listed; 512 GB / 1 TB marked fully reserved |
| CloudMac annual | $999/year | Effective $83.25/month; $141 less per year than 12 × $95 at the Figment entry price |
| MacStadium M4.S | $149/month | M4, 16 GB / 256 GB; pricing page lists support and a 99.99% uptime claim |
Worth stating clearly: CloudMac's $999/year works out below twelve months at Figment's $95 entry price, and its monthly $99.99 plan specifies M4/16 GB/256 GB — a specific M-series chip at that price. Figment's $95 is an entry price for an unspecified M-series configuration, so a direct chip-for-chip comparison is only valid against the configuration you are quoted. Where Figment is useful is the specific managed CI work in the $249 and $999 managed plans: the runner setup, the backups and the monitoring are included, not an add-on.
After the build, two tools confirm the architecture:
lipo -archs .build/arm64-apple-macosx/release/"$BIN"
# arm64
file .build/arm64-apple-macosx/release/"$BIN"
# ... Mach-O 64-bit executable arm64
A universal binary shows both slices under lipo -archs: x86_64 arm64. Use grep -w arm64 (word boundary) to avoid a false match against arm64e.
Add this as the final step of the job. It fails if the runner itself is translated under Rosetta, or if any provided binary lacks an arm64 slice. The script exits on the first error, so a failed compile cannot be reported as a passing architecture check.
#!/usr/bin/env bash
# verify-arm64.sh — post-build arm64 slice guard
# Usage: bash verify-arm64.sh <binary> [<binary> ...]
set -euo pipefail
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <binary> [<binary> ...]" >&2
exit 2
fi
# Rosetta guard: sysctl.proc_translated == 1 means this shell is translated.
translated="$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)"
if [ "$translated" = "1" ]; then
echo "ERROR: CI runner is executing under Rosetta; arm64 output cannot be trusted." >&2
exit 1
fi
status=0
for binary in "$@"; do
if [ ! -f "$binary" ]; then
echo "ERROR: file not found: $binary" >&2
status=1
continue
fi
archs="$(lipo -archs "$binary")"
if printf '%s\n' "$archs" | tr ' ' '\n' | grep -qw arm64; then
echo "PASS arm64 slice present — $binary ($archs)"
else
echo "FAIL arm64 slice missing — $binary ($archs)" >&2
echo " Add ARCHS=arm64 ONLY_ACTIVE_ARCH=NO to xcodebuild," >&2
echo " or --arch arm64 to swift build." >&2
status=1
fi
done
exit "$status"
Invoke it after the build step with the real artifact path from --show-bin-path, not a hard-coded guess. This is an unexecuted example; run it against your own project before trusting the exit code in a release job.
arm64e is an extended variant that uses pointer authentication and is reserved for system binaries. For third-party apps shipped through the App Store, Homebrew, or direct download, target arm64. Setting ARCHS=arm64e on a third-party binary causes codesign and notarization failures. Leave the architecture at arm64 unless Apple documentation for a specific use case tells you otherwise. If you also run a Windows workstation and want to drive this build remotely, see Xcode on Windows: remote Mac setup.
Run your first native arm64 CI build on an M-series Figment Mac. Start here →
Related guides: