Apple Silicon runners, without a second CI platform.
Manzanita supplies ephemeral macOS machines to GitHub Actions. GitHub remains the control plane for workflow syntax, secrets, logs, artifacts, approvals, and job queues.
Quickstart
- Sign in with GitHub and open the dashboard's GitHub page. Install the Manzanita GitHub App on only the repositories that need macOS CI; the installation is verified against your GitHub sign-in before it is connected, and only an organization owner can connect an organization.
- Private repositories selected in the installation are enabled for review. Public repositories start disabled and require an explicit opt-in in the GitHub connection page. Installing the App activates one best-effort trial runner automatically. The trial ends after 14 days or 300 build minutes, whichever comes first. Its clock begins with the first runner job, not at sign-in.
- Change one line in your workflow:
-
Keep an instant fallback during the pilot: set a repository variable
named
MACOS_RUNNERtomanzanita-standard, then use${{ vars.MACOS_RUNNER || 'macos-15' }}inruns-on. Switching the variable routes the next build without a workflow commit.
jobs:
test:
runs-on: manzanita-standard
steps:
- uses: actions/checkout@v5
- run: swift test
No proprietary workflow file is required. Existing GitHub Actions, environments, caches, artifacts, and repository secrets continue to work.
Runner labels and concurrency
| Label | Profile | Capacity |
|---|---|---|
manzanita-standard |
M4 Pro · 6 vCPU · 22 GB | Moving default after advance notice · Xcode 26.6 |
manzanita-xl |
M4 Pro · 12 vCPU · 44 GB | One concurrent full-host job · Xcode 26.6 |
manzanita-xcode16 |
M4 Pro · 6 vCPU · 22 GB | Compatibility channel · Xcode 16.4 |
Simulator matrix
| Runtime | Prewarmed device | Contract |
|---|---|---|
| iOS 17.5 | iPhone 15 Pro workflow destination | Installed and qualified with unchanged Tealium tests |
| iOS 18.6 | Available to workflows | Installed and covered by the machine-readable image contract |
| iOS 26.5 | iPhone 17 Pro | Installed, booted once during image build, then shut down cleanly |
| tvOS 26.5 | Apple TV workflow destination | Installed and qualified with unchanged Tealium tests |
| watchOS 26.5 | Series 11, Ultra, and SE device types | Installed and qualified with an unchanged Watch app build |
Other device types exposed by an installed runtime may be created by a workflow, but only the entries above are covered by the current image contract. VisionOS is not currently installed. Consult the current Standard manifest when checking workflow compatibility.
The app's deployment target and the simulator runtime are different compatibility signals. An app that targets iOS 13 can compile cleanly with Xcode 26.6 while this image still exercises it only on iOS 26.5. Teams that require behavioral coverage on older iOS releases should keep those matrix lanes on another runner until Manzanita publishes the corresponding runtime in this table.
Differences from GitHub macos-15
| Area | Manzanita Standard | Migration note |
|---|---|---|
| Xcode | One pinned Xcode per immutable channel | Use manzanita-xcode16 when 16.4 is required; do not select an arbitrary preinstalled version. |
| PATH aliases | python and python3; Homebrew arm64 paths enabled |
Prefer explicit tool versions in workflows. |
| Ruby tooling | CocoaPods and Fastlane installed; xcpretty is not part of the current contract |
Install unlisted gems in the workflow or pin them with Bundler. |
sudo |
Available for ordinary CI setup; every VM is disposable | Do not rely on host persistence between jobs. |
| Keychain | Login keychain is unlocked for the job; no signing identity is preinstalled | Unsigned archives are ordinary build output, but code signing, notarization, TestFlight/App Store upload, and release secrets remain unsupported in the pilot. |
| Job lifetime | Maximum two hours per job | Split longer workflows into dependent jobs. |
This pilot boundary is a security and support decision, not an Xcode limitation. GitHub Actions secrets can reach an authorized runner, but Manzanita does not yet claim the audited import, isolation, and destruction lifecycle required for signing identities. Keep release jobs on a protected runner and use Manzanita for build, test, and GUI validation until that contract is published.
A Standard subscription runs one 6-vCPU/22-GB job at a time. Add Standard runners for parallel jobs. XL runs one 12-vCPU/44-GB job at a time and consumes the full host. Jobs above purchased concurrency remain queued in GitHub, where their normal logs and status stay visible.
Images and toolchains
Select macOS 15 or macOS 26, an installed Xcode version, and Standard or XL hardware. The selector generates the workflow configuration and shows the current software inventory. Exact Xcode selectors never substitute a different version.
The Standard image is immutable and validated on staging capacity before production nodes pull it. The exact digest used by each job is visible in the Jobs dashboard. Image changes are announced and retain an internal rollback image during migration. Existing labels remain supported; explicit OS selectors stay within the selected macOS major version.
Current default:
Current Standard image, macOS
Tahoe 26.6.2 with Xcode 26.6. Use
manzanita-xcode16 only for projects that still require
Xcode 16.4. Both channels are immutable and digest-pinned.
Available opt-in image: macOS 26 with exact Xcode 26.2, 26.5, and 26.6. It is prewarmed, has passed compatibility qualification, and is available through its exact Xcode selectors or immutable digest pin. The current default does not move.
Xcode 27 opt-in:
macOS 26.6.2 with Xcode 27.0 and the iOS 27.0 Simulator. Select it with
runs-on: [manzanita-standard, manzanita-xcode-27.0].
Jobs without the Xcode selector continue to use Xcode 26.6.
| Component | Standard image v7 | ||
|---|---|---|---|
| 2026-09-09 | macos-26-xcode-26.2-26.5 |
Available · opt-in | Adds exact Xcode 26.2 and 26.5 while retaining Xcode 26.6 as the image default. Vultisig and BeeSwift compatibility runs passed. Xcode 26.2 is qualified for macOS and iOS lanes; its matching watchOS, tvOS, and visionOS runtimes are not installed. |
| Architecture | Apple Silicon arm64 | ||
| macOS | Tahoe 26.6.2 | ||
| Xcode / Swift | 26.6 (17F113) / 6.3.3 | ||
| Python | 3.14.7; both python3 and python |
||
| Node.js / Ruby / Java | 24.20.0 / 4.0.6 / 17.0.20.1 | ||
| CocoaPods / Fastlane | 1.17.0 / 2.239.0 | ||
| Locale / time zone | en_US.UTF-8 / UTC |
||
| GUI contract | Logged-in Aqua session, Accessibility enabled, persistent scrollbars, animations disabled |
Download the machine-readable current Standard manifest. The stable URL follows the current default; immutable versioned manifests remain available for audit and migration checks. Each version records its lifecycle status and replacement without exposing provider-internal registry addresses.
Image release and Xcode policy
- An image ID is immutable. Patch-level macOS or Xcode changes create a new image ID and pass staged compatibility checks before promotion.
- Default-channel changes, including macOS patches and Xcode updates, are qualified and announced at least 7 days before rollout. Select an OS and exact Xcode in the image picker, or pin the complete image digest to keep every installed package fixed.
- During the pilot, planned image changes and deprecations are published in this release history and sent directly to affected workspace owners before the default label moves. The control plane refuses to admit a new default digest until its notice window has elapsed and notifications have been accepted by the email provider. This send record is not a human read receipt.
- A digest pin never silently moves to a different image. After an image is superseded, it remains runnable for at least 30 days. Retirement requires a separate notice at least 7 days before the deadline. Already accepted jobs finish on their assigned image.
-
Choose
manzanita-standardfor Standard jobs,manzanita-xlfor full-host jobs, ormanzanita-xcode16for Xcode 16 compatibility. Image revisions identify release history; they are not workflow labels. Each accepted job keeps its assigned image through later updates.
Release history
| Date | Image | Status | Change |
|---|---|---|---|
| 2026-09-05 | macos-tahoe-xcode-26.6-v4 |
Default | Adds iOS 17.5 and 18.6, tvOS 26.5, and watchOS 26.5 runtimes; qualified against unchanged Cuckoo and Tealium build and test workflows, including a Watch application. |
| 2026-09-05 | macos-tahoe-xcode-26.6-v2 |
Superseded · retained in release history | Adds the checksum-pinned GitHub macOS 26 arm64 Rust baseline and repository-selected Rust toolchains; qualified against TablePro's unchanged universal Rust bridge build. |
| 2026-09-04 | macos-tahoe-xcode-26.6-v1 |
Superseded | Moves Xcode 26.6 to its Apple-supported Tahoe host, adds the conventional Xcode and Python paths, and qualifies real Sparkle and Rectangle application builds. |
| 2026-09-04 | macos-sequoia-xcode-26.6-v3 |
Superseded |
Adds the conventional /Applications/Xcode.app
path and a bounded CI-only xed compatibility shim;
qualified against unchanged Tealium and Cuckoo entry points.
|
| 2026-09-03 | macos-sequoia-xcode-26.6-v1 |
Superseded | Xcode 26.6, Swift 6.3.3, iOS 26.5 simulator runtime, and an immutable Xcode 16.4 compatibility channel. |
| 2026-09-03 | macos-sequoia-xcode-16.4-v2 |
Pilot | Python compatibility alias, deterministic GUI preferences, and non-interactive Git credential policy. |
Job lifecycle
- The GitHub App receives
workflow_job.queued. - The scheduler selects an entitled slot and issues a short-lived JIT runner token.
- The node clones a prewarmed golden image using APFS copy-on-write.
- The VM connects directly to GitHub, runs exactly one job, and uploads logs and artifacts there.
- The VM and token are destroyed. A clean replacement is prepared.
Caching
Use actions/cache exactly as on GitHub-hosted runners;
nothing Manzanita-specific is required. Persistent provider-managed
cache volumes are not yet part of the product, so explicitly cache
the paths your build needs.
- uses: actions/cache@v4
with:
path: |
.build
~/Library/Developer/Xcode/DerivedData
key: ${{ runner.os }}-${{ runner.arch }}-spm-${{ hashFiles('Package.resolved') }}
Security model
- One GitHub job per disposable VM, with no host-directory mounts.
- Repository-scoped GitHub App access and short-lived JIT registration tokens. A job can read only its own repository by default.
- An organization owner must explicitly map each source repository to any private package repository it needs. The mapping is applied per job and is never issued to a fork pull-request job.
- A per-VM network policy plus a host firewall block the provider network, peer machines, and host management services while preserving public build dependencies.
- The worker fails closed if its firewall is absent, and the isolation test is rerun after networking or runtime changes.
- Workspace and job credentials are destroyed after every job.
- Node control is outbound-only; customers do not expose inbound ports.
- Audit events cover authentication, billing, and node enrollment.
Private package example
For an app such as acme/mobile-app that depends on
acme/private-sdk, open Dashboard → GitHub → Private
dependencies, choose acme/mobile-app under “Jobs from,”
select acme/private-sdk, and save.
This does not grant the reverse relationship or access from any other
enabled repository.
Manzanita is dedicated-host CI infrastructure, not confidential computing. Sensitive secrets remain governed by GitHub Actions permissions and your workflow design. Do not expose deployment or signing secrets to untrusted pull-request code.
Billing and fair use
| Plan | Price | Capacity |
|---|---|---|
| Standard | $149/month | 6 vCPU / 22 GB; one concurrent job |
| XL | $299/month | 12 vCPU / 44 GB; one concurrent job |
Plans are flat-rate and have no metered overage charges. To protect responsive queues, usage above 15,000 minutes per runner in 30 days starts a capacity review. During contention, lower-utilization organizations receive scheduling priority while running jobs are never preempted.
The free trial is one best-effort Standard runner for 14 days or 300 build minutes, whichever happens first. Trial jobs may wait behind paid runner capacity. Stripe owns cards, invoices, cancellation, and the Customer Portal. Plans are billed monthly, require no annual prepayment, and may be cancelled at any time.
This invitation-only pilot has no contractual SLA. The first paid monthly charge is refundable within seven calendar days; later monthly charges follow the cancellation and refund rules in the pilot terms.
Troubleshooting
Keep a one-click GitHub-hosted fallback
During the pilot, set a repository variable such as
MACOS_RUNNER and use it for runs-on. Set it
to manzanita-standard normally; change it to
macos-15 to route the next run back to GitHub without a
workflow commit.
jobs:
test:
runs-on: ${{ vars.MACOS_RUNNER || 'manzanita-standard' }}
steps:
- uses: actions/checkout@v5
- run: swift test
Job stays queued
Confirm the GitHub App is installed for that repository and the repository is enabled on the dashboard's GitHub page, the label is spelled exactly, and your organization has an active trial or subscription. Jobs also queue normally when all purchased slots are busy.
Toolchain mismatch
Print xcodebuild -version and
swift --version in the job. Pinning is deliberate;
contact support before assuming xcode-select points at
another beta.
Need help
Email hello@manzanita.run with the GitHub job URL, repository name, and approximate queue time. Never send repository or signing secrets.
Pilot service targets
These are operational targets for the invitation-only pilot, not a contractual SLA: p95 runner acquisition below two minutes, infrastructure-caused job failure below 1%, and acknowledgement of support email within four business hours (Pacific time). Active incidents and image changes are communicated to affected workspace owners by email. GitHub remains the source of truth for job logs and status.
How we measure time to runner
Every timing figure on this site and in the dashboard comes from the
/api/dashboard/observability metrics over a rolling
30-day window of your own jobs (up to 1,000 most recent samples).
Three intervals are reported separately and never added together
silently:
- Capacity wait: Manzanita received the queued job until a dispatcher claimed a runner for it. This is time spent behind your purchased concurrency.
- VM start (time to runner): the dispatcher began provisioning until the disposable VM was booted and ready. The "5 s median" on the landing page is this interval, measured across the production fleet for the 30 days ending September 5, 2026 (p95 9 s). It excludes capacity wait and GitHub's own job matching.
- Queue to start: GitHub queued the job until GitHub marked it in progress. This is the wall-clock wait a developer experiences and includes both intervals above plus GitHub matching.
Your own numbers for all three are on the Health & queue page, with sample counts, and per job on the Jobs page.
© 2026 Manzanita · Menlo Park, California.