Apple & Xcode CI

Fast Xcode CI without owning a Mac.

Build, sign and test iOS apps on Apple silicon we own, a sixth of GitHub’s macOS minute, none of Xcode Cloud’s GUI lock-in. Adopt it as a drop-in macOS runner or call the build API directly, and install the result straight onto a simulator.

sandal xcode build . --scheme MyApp
per macOS minute
$0.01
per macOS minute
our own racks
Apple silicon
our own racks
faster iOS build
2×
faster iOS build
metering
per second
metering
The problem

Mac CI is slow, expensive, or walled in.

GitHub bills macOS at ten times a Linux minute. Xcode Cloud is a GUI pipeline you can’t version or port, on hardware it won’t name. A Mac mini under a desk is a single point of failure. iOS teams deserve better than all three.

  • Mac ownership

    The 10× macOS tax

    Hosted Mac minutes are the most expensive line in most iOS pipelines. Owning the racks removes the markup.

  • Platform control

    Lock-in and opacity

    Xcode Cloud hides the hardware and locks config in a GUI. A build should be a versioned call, and you should know the chip.

  • Build performance

    Cold, serial builds

    Without a warm cache and warm hosts, every clean build pays the full DerivedData and SPM cost again.

How it works

Swap a label, or call the API.

Adopt it however your pipeline is shaped, a one-line runner change, or a direct build call that returns a signed artifact.

ios.yml−1 +1Drop-in macOS runner
build:
- runs-on: macos-15
+ runs-on: sandal-macos-m4
steps:
- uses: actions/checkout@v4
- run: xcodebuild test -scheme App
  1. 01

    Point at our Macs

    Change runs-on to sandal-macos-m4, or call sandal.builds.xcode.run from anywhere. Same Xcode, same fastlane, same signing.

  2. 02

    Sync only the diff

    Source is content-addressed; a second build ships kilobytes. DerivedData, SPM and CocoaPods restore from a warm cache keyed by your dependency graph.

  3. 03

    Build & sign

    Compile on dedicated Apple silicon in an ephemeral VM. Signing material is a grant id, decrypted into the keychain at build time and destroyed with the VM.

  4. 04

    Ship the artifact

    Get the .ipa, .xcresult and dSYM behind a signed URL or in your own bucket, or install the .app straight onto a live simulator.

Capabilities

A real Mac pipeline, none of the drag.

  • 01

    Drop-in runner

    One runs-on change; every action, cache and secret keeps working.

  • 02

    Build API

    Kick off a signed build from Linux, a queue worker or an agent, no macOS runner required.

  • 03

    Signing grants

    Register certificates once; build requests carry an id, never the secret.

  • 04

    DerivedData cache

    A 340k-line incremental build drops from 9m 12s to 2m 38s on a warm cache.

  • 05

    Build → simulator

    attachTo installs and launches the fresh .app on a device for review or an agent loop.

  • 06

    Portable config

    A build is a versioned call in your repo, not a GUI pipeline locked to one vendor.

Built on Sandal

The products that power it.

Apple CI is powered by Sandal’s Mac build and runner surfaces.

Live desktop stream
CodeFileEditSelectionViewGoTerminalWindowHelpWed 16:45
agent.py · sandbox
Explorersandboxagent.pybrowser.pyvision.pytestsREADME.md

1from sandal import Sandbox

2

3# boot a desktop the agent can see

4vm = Sandbox.create(desktop=True)

5vm.screenshot("before.png")

6

7vm.click(x=412, y=280)

8vm.type("hello from the agent")

9vm.key("cmd+s")

10

11# read the screen back

12frame = vm.stream.latest()

13assert frame.contains("Saved")

zsh, sandal

➜ sandbox sandal desktop --stream

connecting to vm_7c31…

✓ stream open · 60fps · 18ms latency

screenshot → before.png

click(412, 280) · type · cmd+s

✓ saved

➜ sandbox

60fps
Get started

Move your iOS pipeline off the hosted Mac.

Apple silicon we own, a sixth of GitHub’s minute, no lock-in. Swap one line or call the API.

sandal xcode build . --scheme MyApp