GitHub Actions on NoMac · NoMac

GitHub Actions on NoMac

Your GitHub Actions jobs on a fresh Mac with Xcode: change runs-on to nomac.

1. Connect GitHub

Sign in at nomac.app, open CI and press Connect GitHub. It installs the NoMac CI app on your account or organization, on every repository or the ones you pick. You need some credit.

2. Change one line

.github/workflows/ci.yml

jobs:
  test:
    runs-on: nomac
    steps:
      - uses: actions/checkout@v5
      - name: Build and test
        run: |
          set -o pipefail
          xcodebuild test -scheme App -destination \
            'platform=iOS Simulator,name=iPhone 17 Pro,OS=27.0' \
            | xcbeautify

Each job gets its own Mac. Keep set -o pipefail when you pipe into xcbeautify: GitHub's default shell has no pipefail, so a failed test would otherwise pass. The CI page lists every job with its minutes and cost.

What you get

The machine

A fresh macOS VM for every job, deleted when the job ends: 6 cores and 8 GB of an Apple silicon Mac (M4 to M6).

Installed

macOS 26.6.2, Xcode 27.0, iOS 27.0 Simulator; Node 24.20.0, npm 11.19.0, Ruby 3.4.11, Bundler 4.0.23, CocoaPods 1.17.0, xcbeautify 3.2.1, XcodeGen 2.46.0, Git 2.54.0, Git LFS 3.8.0, GitHub CLI 2.100.0, jq 1.7.1, Python 3.14.7, uv 0.13.0, simulator-use 0.15.1, nvm 0.40.8, Swift 6.4, Homebrew 7.0.11.

Price

1.5¢ a minute ($0.90 an hour) from your NoMac credit, billed per minute from the Mac's start to its stop, a minute at least. No subscription. A Mac that ran no job is credited back.

Jobs at once

2 per account; a job waits in GitHub's queue for a free one. Ask for more.

Longest job

6 hours, as on GitHub's own runners.

Out of credit

Jobs wait in GitHub's queue and start once you buy credit (GitHub cancels a job queued for 24 hours).

Build incrementally: nomac-cache

Set runs-on to nomac-cache and the job gets its repository's build cache, a 20 GB disk kept between jobs. It holds the checkout, DerivedData, the tool cache of setup actions, and the Swift package, CocoaPods, Carthage, Homebrew, npm, Yarn and pnpm caches. Checkout then fetches only new commits, and Xcode rebuilds only what they changed.

.github/workflows/ci.yml

jobs:
  test:
    runs-on: nomac-cache

Sign and send to TestFlight

With the App Store add-on, a release workflow signs and sends to TestFlight with NoMac's steps, and no Apple secret is stored in GitHub: NoMac keeps the App Store Connect key and makes the profiles.

Terminal

permissions:
  id-token: write
  contents: read
jobs:
  release:
    runs-on: nomac
    steps:
      - uses: actions/checkout@v5
      - uses: NoMac-app/actions/sign@v1
      - run: |
          xcodebuild archive -scheme App -configuration Release -destination 'generic/platform=iOS' -archivePath build/App.xcarchive -xcconfig "$NOMAC_XCCONFIG"
          xcodebuild -exportArchive -archivePath build/App.xcarchive -exportPath build/export -exportOptionsPlist "$NOMAC_EXPORT_OPTIONS"
      - uses: NoMac-app/actions/testflight@v1
        with:
          ipa: build/export/App.ipa

Secrets and safety

A CI Mac runs only the job: it gets the job's secrets from GitHub, and none of your NoMac secrets or drives; signing reaches it only through the sign step of a workflow you allowed. Nothing else reaches it, including your NoMac API keys, and it is deleted when the job ends. A nomac-cache job also gets its repository's build cache, never any other drive, and a job that signed never updates the cache.

Any other CI: nomac run

From GitLab, Buildkite, Bitbucket, Jenkins or a script, nomac run runs one command on a fresh Mac of its own and stops the Mac after. It uploads the folder as nomac sync does (not .git, build output, dependency folders or secret files), prints the command's output on stdout as it arrives, exits with its code, and copies back what --pull names, also when the command failed. A cancelled job (SIGTERM, SIGINT or SIGHUP) stops the Mac too.

Terminal

# .gitlab-ci.yml
ios-tests:
  image: node:24
  script:
    - npx -y @nomac/cli run --pull build/tests.xcresult -- xcodebuild test -scheme App -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -resultBundlePath build/tests.xcresult
  artifacts:
    when: always
    paths: [build/tests.xcresult]