為什麼 iOS 建置離不開專屬 macOS 環境?
與 Android 可以在 Linux 容器裡跑 Gradle 不同,Apple 的建置工具鏈——xcodebuild、程式碼簽名、notarytool、App Store Connect API——全部綁定 macOS 與 Apple Silicon / Intel 實體機。這意味著每一家有 iOS 產品的團隊,遲早要面對「誰來提供這台 Mac」的問題。
常見痛點可以歸納為三類:公共 Runner 排隊(GitHub Actions 免費 macOS 額度稀缺,高峰時段 workflow 可能等待 20–45 分鐘才分配到機器)、本機開發機被 CI 佔用(同事 push 程式碼後你的 Xcode 編譯速度驟降、風扇狂轉)、憑證與金鑰管理混亂(多台機器各自匯入 .p12,過期後無人知曉)。雲端專屬 Mac 節點可以同時解決前兩個問題,並為第三個問題提供單一可信環境。
硬體:Mac mini M4 · 10 核 CPU · 16 GB 統一記憶體 · 256 GB SSD · 1 Gbps 獨享頻寬(SpinMac 日本節點)。
系統:macOS 15 Sequoia,Xcode 16.4。專案:SwiftUI 中型 App(約 12 萬行、含 3 個 Extension target)。
CI 工具:GitHub Actions self-hosted runner 2.323.0、Jenkins 2.479 LTS + macOS agent。
簽名:Apple Distribution 憑證 + App Store Connect API Key(Issuer ID + Key ID + .p8)。
三種 CI 方案對比:該選哪一種?
在動手裝 Xcode 之前,先明確你的團隊處於哪條路徑上。三種方案沒有絕對優劣,差異在於排隊容忍度、月建置次數、合規要求。
| 方案 | 典型成本 | 排隊 / 並發 | 適合團隊 |
|---|---|---|---|
| GitHub 託管 macOS Runner | 按分鐘計費(約 $0.08/min 起) | 共享池,高峰排隊明顯 | 月建置 < 500 分鐘、可接受等待 |
| 自購 Mac mini 放機房 | 硬體 $599+ 一次性 + 電費維運 | 獨佔,但需自行維護系統與憑證 | 有固定辦公場所、長期高頻建置 |
| 雲端獨享 Mac(按天租用) | SpinMac $21.2/天起,無合約 | 實體機獨佔,即時可用 | 中小團隊、階段性發版、遠端協作 |
我們的實測專案在中型 SwiftUI 工程上,xcodebuild archive 全量建置在 M4 節點耗時約 4 分 12 秒(含 Swift 編譯與連結);同一專案在 GitHub 託管 macOS-14 Runner 上,排隊 18 分鐘後實際建置 5 分 40 秒——總耗時接近 24 分鐘。對於需要「push 即測」的團隊,排隊時間往往比建置本身更影響交付節奏。
雲端節點環境初始化:Xcode 與簽名材料
SpinMac 交付的 Mac mini 已預裝完整 macOS 並擁有管理員權限。開通後透過 SSH 或瀏覽器 VNC 登入,按以下順序完成 CI 前置配置。建議把憑證與描述檔集中存放在固定路徑,後續 workflow 腳本統一引用。
-
01
安裝 Xcode 與命令列工具
從 App Store 或
xcode-select安裝 Xcode 16.x,執行sudo xcodebuild -license accept與xcodebuild -runFirstLaunch。驗證:xcodebuild -version應輸出正確版本號。 -
02
匯入簽名憑證與 Provisioning Profile
將 Distribution .p12 與密碼透過安全通道傳到
~/certs/,用security import匯入鑰匙圈;.mobileprovision 檔案放入~/Library/MobileDevice/Provisioning Profiles/。CI 環境建議使用專用鑰匙圈並設定-T /usr/bin/codesign信任。 -
03
配置 App Store Connect API Key
在 Apple Developer 後台建立 API Key,將
AuthKey_XXXXXX.p8存至~/private_keys/。上傳 TestFlight 時配合xcrun altool或fastlane pilot upload,避免互動式 Apple ID 登入。 -
04
複製儲存庫並快取 DerivedData
首次
git clone後執行一次本機 Archive,確認簽名鏈路通暢。將 DerivedData 保留在本機 SSD(256 GB 對中型專案足夠),後續增量建置可節省 30–50% 時間。
無頭環境(SSH / Runner)下 codesign 失敗,90% 是鑰匙圈未解鎖或未授權。在建置腳本開頭加入:security unlock-keychain -p "$KEYCHAIN_PASSWORD" ~/Library/Keychains/ci.keychain-db,並設定 set-key-partition-list 允許 codesign 存取私鑰。切勿把密碼明文寫進 Git 儲存庫——用 GitHub Secrets 或 Jenkins Credentials 注入。
掛載 GitHub Actions 自託管 Runner
專屬 Runner 註冊後,workflow 可透過 runs-on: self-hosted 或自訂 label(如 macos-m4)定向到這台雲端 Mac,徹底繞開公共池排隊。以下步驟在 SpinMac 節點上實測通過。
在 GitHub 儲存庫 Settings → Actions → Runners → New self-hosted runner,選擇 macOS ARM64,按頁面提示下載並解壓 actions-runner 套件,然後執行:
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token RUNNER_TOKEN --labels macos-m4,ios-build --unattended
註冊完成後安裝為系統服務,確保節點重啟後 Runner 自動上線:sudo ./svc.sh install → sudo ./svc.sh start。在 workflow YAML 中指定 label:
runs-on: [self-hosted, macos-m4]
典型 iOS 建置 job 核心步驟包括:檢出程式碼 → 解鎖鑰匙圈 → xcodebuild archive → xcodebuild -exportArchive → xcrun altool --upload-app 或 Fastlane upload_to_testflight。我們在 M4 節點上跑通的一條 Fastlane lane 從 push 到 TestFlight 處理完成(含 Apple 側排隊)平均約 11 分鐘,其中本機建置與上傳僅佔 6 分鐘出頭。
自託管 Runner 能存取儲存庫程式碼與簽名金鑰,務必:限制儲存庫 collaborator 權限、定期輪換 Runner registration token、不要在 fork PR 上自動觸發帶金鑰的 workflow(使用 pull_request_target 需格外謹慎)。團隊共用節點時,可為不同專案註冊不同 Runner 或配合 OpenClaw 沙箱隔離 Agent 操作範圍。
Jenkins macOS Agent 配置要點
若團隊已有 Jenkins 控制器(可執行在 Linux 上),macOS 建置能力透過 Agent 節點接入。在 SpinMac 雲端 Mac 上安裝 JDK 17 與 Jenkins agent.jar,以 LaunchDaemon 方式常駐,控制器透過 SSH 或 JNLP 拉取任務。
Jenkins 的優勢在於流水線視覺化與外掛生態:Xcode 外掛、Credentials Binding、AnsiColor 日誌著色、建置產物歸檔等。典型 Pipeline 片段邏輯為:在 stage('Archive') 呼叫 sh 'xcodebuild ...',在 stage('Upload') 呼叫 Fastlane 或 altool。將 DEVELOPER_DIR 固定為 /Applications/Xcode.app/Contents/Developer,避免多版本 Xcode 切換混亂。
與 GitHub Actions 相比,Jenkins 更適合多分支、多環境、需要人工審批閘門的企業內部流程。雲端 Mac 按天租用可以當作「彈性 Agent」:發版週開通節點、淡季釋放,不必為全年 365 天維運一台機房 Mac。
Archive、簽名與 TestFlight 上傳實戰
無論使用哪套 CI,最終產物鏈路一致:Archive 產生 .xcarchive → Export 產生 .ipa → 上傳 App Store Connect。命令列方式(不依賴 Xcode GUI)是 CI 的標準做法。
Archive 範例(Release 配置、指定 scheme 與輸出路徑):
xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -archivePath build/MyApp.xcarchive CODE_SIGN_STYLE=Manual PROVISIONING_PROFILE_SPECIFIER="MyApp AppStore"
Export 需配套 ExportOptions.plist(method 設為 app-store),然後:
xcodebuild -exportArchive -archivePath build/MyApp.xcarchive -exportPath build/export -exportOptionsPlist ExportOptions.plist
上傳 TestFlight 建議使用 API Key 方式,避免雙因素驗證在無人值守環境卡住:
xcrun altool --upload-app -f build/export/MyApp.ipa -t ios --apiKey KEY_ID --apiIssuer ISSUER_ID
在中型專案上,M4 的 10 核 CPU 使 Swift 並發編譯明顯快於舊款 Intel Mac CI 機;16 GB 統一記憶體在全量 clean build 時仍有餘量,未觸發 swap。若專案含大量 Swift Package 依賴,建議開啟 -clonedSourcePackagesDirPath 快取目錄以縮短後續建置。
沒有辦公室機房,怎麼穩定跑 iOS CI?
很多獨立開發者和小團隊的真實困境是:需要 macOS 建置,但不想為了一台 CI 機專門租辦公室、拉網路線、處理斷電與系統升級。公有雲虛擬機無法滿足(沒有完整 macOS 與 Apple 簽名鏈),二手 Mac mini 放家裡又面臨上行頻寬不穩、IP 變動、家人誤關機等問題。
SpinMac 的定位是獨享實體 Mac mini M4:不做虛擬化、不超售,每台機器 16 GB 記憶體與 1 Gbps 獨享頻寬,付款後 1–5 分鐘自動開通。五地節點(新加坡、日本、韓國、中國香港、美國東部)可按目標使用者分布選擇——例如面向日本市場的 App 可選東京節點,縮短上傳 TestFlight 時的跨境延遲。
計費上,按天 $21.2 起、按週 $57.3、按月 $106.1,無長期合約。發版密集的一週開通節點掛載 Runner,日常維護期釋放,往往比全年自購機器加電費更划算。需要多機並行建置時,可選購 Thunderbolt 5 並聯服務組成 80 Gbps 叢集,適合大型 monorepo 或多 App 矩陣同時 Archive。
-
01
在 SpinMac 選擇節點並開通
登入控制台選區域與租期,付款後 SSH 憑證與 VNC 入口自動下發。詳見說明中心。
-
02
按本文第三節完成 Xcode 與簽名配置
建議將鑰匙圈與 API Key 路徑寫入環境變數,供 GitHub Actions / Jenkins 統一讀取。
-
03
註冊 Runner 並觸發首次 pipeline
先跑通 Debug 建置,再切 Release Archive + TestFlight,逐步把 Fastlane 或原生腳本固化進儲存庫。
成本與選型:一張表收束
| 你的情況 | 推薦路徑 | 雲端 Mac 角色 |
|---|---|---|
| 獨立開發者,月發版 1–2 次 | 發版日租 SpinMac + 手動 Archive | 臨時建置機,用完即釋 |
| 5–15 人團隊,每天多次 push | 常駐 self-hosted Runner | 按月租用,獨佔不排隊 |
| 已有 Jenkins,缺 macOS Agent | 雲端 Mac 作彈性 Agent | 峰值擴容,避免買新機 |
| 多 App 矩陣 + 夜間批次建置 | TB5 叢集並聯 | 多臺 M4 並行 Archive |
iOS CI/CD 的技術門檻不在 Xcode 本身,而在穩定的 macOS 算力 + 可復現的簽名環境。公共 Runner 適合低頻建置;自建機房適合有運維能力的成熟團隊;雲端獨享 Mac 填補了「不想排隊、又不想買機器」的中間地帶——按天計費、物理機效能可預期、全球節點就近部署。把編譯與簽名搬到雲上之後,本地 MacBook 可以專心寫程式碼,而不是在深夜被 CI 任務搶走風扇與記憶體。