Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Build arm64 Binaries for Apple Silicon in CI

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.

Why Intel Builds Silently Run Under Rosetta

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.

ARCHS and ONLY_ACTIVE_ARCH for arm64 Targets

Two Xcode build settings decide which slices land in the output.

SettingValueEffect
ARCHSarm64Build only the arm64 slice
ARCHSarm64 x86_64Build a universal binary in one pass
ONLY_ACTIVE_ARCHNORespect ARCHS regardless of host arch
ONLY_ACTIVE_ARCHYESBuild 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: Explicit arm64 Builds

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 Plan Choice and a Worked Price Comparison

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.

VendorHeadlineNotes & Source
Figment M-seriesfrom $95/monthChip/RAM/disk confirmed in the quote; request-based
CloudMac monthly$99.99/monthM4, 16 GB RAM, 256 GB SSD listed; 512 GB / 1 TB marked fully reserved
CloudMac annual$999/yearEffective $83.25/month; $141 less per year than 12 × $95 at the Figment entry price
MacStadium M4.S$149/monthM4, 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.

Verify the Slice with lipo and file

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.

Post-Build arm64 Guard Script

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.

arm64 vs arm64e: Pick arm64 for Distribution

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:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms