1–5 分鐘交付

告別 Runner 排隊,直接跑 iOS 建置

$21.8 / 天起 · 裸機獨享
設定雲端 Mac
M4 · 16 GB Xcode 可固定版本 全球五節點

沒有 Mac 也能跑 iOS CI/CD:GitHub Actions 自託管 Runner 設定指南

GitHub 託管 macOS Runner 尖峰時段排隊動輒 20 分鐘,Xcode 版本也不由你決定。這篇從零說清楚:如何在 VPSRox 裸機 Mac mini M4 上安裝自託管 Runner、完成 Xcode Archive 與程式碼簽署、用 fastlane 自動推送 IPA 到 TestFlight,並對照 Jenkins Agent 方案的取捨。

為什麼自託管 Runner 比 GitHub 託管 Runner 更適合 iOS 建置?

GitHub Actions 提供的 macOS 託管 Runner 理論上免去了硬體維護,但實際生產中會碰到幾個難以回避的問題。

首先是等待時間。GitHub 的 macOS Runner 池容量遠小於 Linux,高峰時段(UTC 12:00–20:00,對應亞太工作時間)從 runs-on: macos-latest 觸發到 Runner 就緒,排隊時長中位數在 8–15 分鐘,偶爾超過 20 分鐘。對於一個 Clean Build 本身只需 6 分鐘的中型 SwiftUI 專案來說,等待時間已經超過實際編譯時間。

其次是Xcode 版本不可控macos-latest 標籤會隨 GitHub 基礎設施升級靜默切換 Xcode 版本,歷史上曾出現 Xcode 15 → 16 的無通知升級導致專案建置失敗,需要手動追加 xcode-select 步驟並等待大版本相容。

第三是免費額度消耗快。macOS Runner 的計費權重是 Linux 的 10 倍(GitHub 官方檔案),免費帳戶每月 2000 分鐘額度在 macOS 上折算僅 200 分鐘,稍微密集一點的迭代週期就會超額計費。

相比之下,自託管 Runner 接在你自己的 Mac 上可以徹底規避以上三點:Runner 永遠線上、Xcode 版本由你維護、執行時間不計費。唯一的門檻是你需要有一臺持續執行的 macOS 機器——這正是本文要解決的問題。

8–20 min
GitHub 託管 macOS Runner 高峰排隊時長
< 30 s
自託管 Runner(線上 Mac)job 啟動延遲
10×
macOS 對 Linux 計費倍率(GitHub 官方)
固定
自託管節點 Xcode 版本由你完全掌控

準備工作:開通雲端 Mac 節點與基礎環境

在任何 iOS CI/CD 接線之前,你需要一臺能 SSH 進去、24 小時線上的 macOS 機器。本文以 VPSRox 獨享 Mac mini M4 節點為例(也適用於物理 Mac mini / Mac Studio),以下步驟在 VPSRox 控制台交付後 5 分鐘內均可完成。

SSH 連線與 Xcode Command Line Tools

  1. 01
    設定 SSH 金鑰認證

    在控制台複製公網 IP,本機執行:

    ssh-copy-id -i ~/.ssh/id_ed25519.pub user@<節點IP>

    完成後停用密碼登入(PasswordAuthentication no),防止暴力破解。

  2. 02
    安裝 Xcode Command Line Tools

    xcode-select --install

    如需完整 Xcode GUI(用於 Archive 簽署),從 developer.apple.com/downloads 下載 .xip 檔案,解壓後移動到 /Applications/ 並切換路徑:

    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

  3. 03
    安裝 Homebrew

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

    安裝完成後根據提示將 /opt/homebrew/bin 加入 PATH,寫入 ~/.zprofile

  4. 04
    驗證 Xcode 建置鏈

    xcodebuild -version

    確認輸出 Xcode 版本號與 Build Version,後續 YAML 中可用 XCODE_VERSION 環境變數鎖定版本。

推薦:用 xcodes 工具管理多版本 Xcode

透過 brew install xcodesorg/made/xcodes 安裝 xcodes,可以快速切換 Xcode 版本,無需手動修改 xcode-select 路徑。在 CI 工作流中用 xcodes select 16.2 精確鎖定版本,杜絕版本漂移。

安裝與註冊 GitHub Actions 自託管 Runner

GitHub 提供的自託管 Runner 安裝包會在 Mac 上執行一個輪詢守護程式,當有 workflow 觸發時拉取 job 到本機執行,整個過程無需 Mac 向外開放埠。

在 GitHub 儲存庫取得註冊權杖

進入目標儲存庫 → SettingsActionsRunnersNew self-hosted runner,選擇 macOS 平台,複製頁面給出的下載命令與註冊權杖(Token 有效期 1 小時)。

  1. 01
    下載並解壓 Runner

    mkdir actions-runner && cd actions-runner

    curl -o actions-runner-osx-arm64-2.319.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-osx-arm64-2.319.1.tar.gz

    tar xzf ./actions-runner-osx-arm64-2.319.1.tar.gz

    注意選擇 osx-arm64 套件(Apple Silicon),版本號以 GitHub 頁面顯示為準。

  2. 02
    註冊 Runner

    ./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO --token <TOKEN> --name mac-m4-vpsrox --labels mac,xcode16,ios

    --labels 自訂標籤稍後在 YAML 的 runs-on 中使用,可以精確路由到這台機器。

  3. 03
    安裝為 launchd 服務(開機自啟)

    ./svc.sh install

    ./svc.sh start

    launchd 會在使用者登入後自動拉起 Runner 程序。查看狀態:./svc.sh status

在 Workflow YAML 中指向自託管 Runner

註冊完成後,在 .github/workflows/ios-ci.yml 裡把 runs-on 替換為你的自訂標籤:

jobs:
  build:
    runs-on: [self-hosted, mac, xcode16]
    steps:
      - uses: actions/checkout@v4
      - name: Show Xcode version
        run: xcodebuild -version
安全警告:公開儲存庫慎用自託管 Runner

自託管 Runner 在公開儲存庫中可能被 fork PR 觸發,惡意程式碼將在你的 Mac 上執行。建議:①僅在私有儲存庫或 Organization 範圍啟用;②在 Runner 的系統帳戶(非管理員)下執行,嚴格限制權限;③定期審查 ~/.bash_history~/actions-runner/_diag/ 日誌。

設定 Xcode 建置環境與相依性管理

一次乾淨的 iOS 建置通常需要解決三件事:依賴拉取、編譯快取、派生資料路徑隔離。

CocoaPods vs Swift Package Manager

如果專案使用 CocoaPods,在 Runner 機器上安裝一次即可:

sudo gem install cocoapods

在 CI 步驟中:

- name: Install CocoaPods
  run: pod install --repo-update
  working-directory: ./ios

使用 Swift Package Manager 時,Xcode 會在首次建置時自動解析依賴。為加快後續 CI 速度,可以在 job 中快取 SPM 解析結果:

- uses: actions/cache@v4
  with:
    path: ~/Library/Developer/Xcode/DerivedData
    key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}

衍生資料路徑隔離

同一臺 Runner 上如果有多個專案併發建置,預設的 DerivedData 路徑會相互干擾。推薦在 xcodebuild 命令中顯式指定派生資料路徑:

xcodebuild \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -sdk iphoneos \
  -configuration Release \
  -derivedDataPath ./DerivedData \
  clean build

並行建置參數調校

Mac mini M4 有 10 核 CPU(4 效能核 + 6 節能核),Xcode 預設並行數通常為核心數。對於大型專案可以適當調低以避免記憶體壓力:

defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 6

實測一個 80 個 Swift 檔案的 SwiftUI 專案,在 16 GB RAM 的 M4 節點上 Clean Build 耗時約 4 分 12 秒,無任何 swap 壓力。

程式碼簽署與鑰匙圈:Archive 打包全流程

程式碼簽署是 iOS CI/CD 流程中最容易出錯的環節。在無圖形介面的 CI 環境裡,預設的 macOS Keychain 行為會與正常開發環境不同,需要顯式管理鑰匙圈。

兩種主流簽署方案對比

方案 適用場景 優點 缺點
手動匯入 p12 憑證 小團隊單機 CI 設定簡單、無需外部依賴 證書輪換需手動重新匯入
fastlane match 團隊協作 / 多台 CI 節點 憑證統一加密儲存、自動輪替 需要 Git 儲存庫或 S3 儲存後端

手動 p12 方案:建立暫時鑰匙圈

在 CI 環境裡最穩妥的做法是每次 job 建立一個臨時鑰匙圈,建置完成後刪除,避免殘留憑據:

# Create a temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain

# Import signing certificate
security import "$CERTIFICATE_P12_PATH" \
  -k build.keychain \
  -P "$P12_PASSWORD" \
  -T /usr/bin/codesign \
  -T /usr/bin/xcodebuild

# Allow xcodebuild to access the key without UI prompt
security set-key-partition-list \
  -S apple-tool:,apple: \
  -s -k "$KEYCHAIN_PASSWORD" build.keychain

其中 KEYCHAIN_PASSWORDP12_PASSWORD 等敏感變數應儲存在 GitHub Repository Secrets 中,透過 ${{ secrets.KEYCHAIN_PASSWORD }} 引用,絕對不能硬編碼在 YAML 檔案裡。

執行 Archive 並匯出 IPA

完整的 Archive → Export 流程分兩步:先用 xcodebuild archive 產生 .xcarchive,再用 -exportArchive 匯出 IPA。

# Step 1: Archive
xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -sdk iphoneos \
  -archivePath ./build/MyApp.xcarchive \
  CODE_SIGN_IDENTITY="Apple Distribution: Your Name (XXXXXXXX)" \
  DEVELOPMENT_TEAM="XXXXXXXX"

# Step 2: Export IPA
xcodebuild -exportArchive \
  -archivePath ./build/MyApp.xcarchive \
  -exportOptionsPlist ExportOptions.plist \
  -exportPath ./build/

ExportOptions.plist 是 Export 步驟的關鍵設定檔案,需要包含 methodapp-store / ad-hoc)、teamIDsigningStyle 欄位。可以先在 Xcode GUI 裡手動 Export 一次,從產生的臨時目錄裡找到該檔案作為模板。

串接 fastlane 自動上傳 TestFlight

IPA 打包出來後,最後一步是把它推送到 App Store Connect 的 TestFlight 通道。fastlane 的 pilot(也叫 upload_to_testflight)是目前最穩定的方案,支援 App Store Connect API Key 認證,無需 Apple ID 密碼。

安裝 fastlane

推薦使用 Bundler 管理 fastlane 版本,避免全域性版本衝突:

# In project root, create Gemfile
source "https://rubygems.org"
gem "fastlane"

然後執行 bundle install,之後所有 fastlane 命令透過 bundle exec fastlane ... 呼叫。

設定 App Store Connect API Key

在 App Store Connect → Users and AccessIntegrationsApp Store Connect API 裡建立 API Key,下載 .p8 檔案,記下 Key ID 和 Issuer ID。

在 GitHub Secrets 中設定以下變數:

  • ASC_API_KEY_ID:API Key 的 ID
  • ASC_API_ISSUER_ID:Issuer ID
  • ASC_API_KEY_CONTENT.p8 檔案的 Base64 編碼內容

Fastfile 設定範例

lane :beta do
  api_key = app_store_connect_api_key(
    key_id: ENV["ASC_API_KEY_ID"],
    issuer_id: ENV["ASC_API_ISSUER_ID"],
    key_content: Base64.decode64(ENV["ASC_API_KEY_CONTENT"])
  )

  upload_to_testflight(
    api_key: api_key,
    ipa: "./build/MyApp.ipa",
    skip_waiting_for_build_processing: true
  )
end

在 GitHub Actions YAML 中呼叫:

- name: Upload to TestFlight
  env:
    ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }}
    ASC_API_ISSUER_ID: ${{ secrets.ASC_API_ISSUER_ID }}
    ASC_API_KEY_CONTENT: ${{ secrets.ASC_API_KEY_CONTENT }}
  run: bundle exec fastlane beta
加速技巧:跳過等待 App Store 處理

skip_waiting_for_build_processing: true 讓 fastlane 在上傳完成後立即返回,不等待 App Store Connect 處理 IPA(通常需要 10–30 分鐘)。如果你的後續步驟不依賴處理結果(如通知 Slack 建置號),這個選項可以顯著縮短 CI 總耗時。

鑰匙圈常見錯誤集中排查

程式碼簽署的錯誤訊息往往語焉不詳,以下是在 CI 無介面環境中最頻繁出現的幾類錯誤及其根因與解法。

錯誤訊息 根因 解法
errSecItemNotFound 憑證未匯入目前使用中的鑰匙圈,或暫時鑰匙圈已被 GC 確認 security default-keychain -s build.keychain 已執行;在 archive 步驟前加 security unlock-keychain
could not find signing certificate for "Apple Distribution" 憑證 Identity 名稱與 Xcode 工程 CODE_SIGN_IDENTITY 不符 執行 security find-identity -v -p codesigning build.keychain 核對完整 Identity 字串並複製貼上
User interaction is not allowed 鑰匙圈被鎖定,codesign 需要彈出 UI 授權對話框(無法在無頭環境彈出) security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain,使 codesign 無需 UI 即可訪問私鑰
Provisioning profile doesn't include the entitlement 描述檔與工程 Entitlements 不符(如 Push Notifications、App Groups) 在 Apple Developer 後台重新產生描述檔,確認已勾選全部 Capability,重新下載安裝
No signing certificate "iOS Distribution" found 描述檔與憑證類型不符(Distribution vs Development) ExportOptions.plist 中確認 method 欄位(app-store 需用 Distribution 證書)

除錯技巧:在建置日誌裡看完整簽署指令

xcodebuild 後加 | xcpretty -r json-compilation-database,或者直接去掉 xcpretty 管道檢視原始日誌,搜尋 CodeSign 關鍵字可以看到實際執行的簽署命令及引數,比 Xcode GUI 的錯誤面板資訊量大得多。

另一個常見的除錯指令是:

codesign -dv --verbose=4 ./build/MyApp.xcarchive/Products/Applications/MyApp.app

直接查看 .app 內嵌的簽署資訊,確認憑證鏈是否完整。

Jenkins Agent 對照:遷移與取捨分析

如果你的團隊已經有 Jenkins 基礎設施,或者因為公司安全策略不能把程式碼推到 GitHub,Jenkins on Mac 是常見的替代方案。兩者的核心差異體現在幾個維度上。

維度 GitHub Actions 自託管 Runner Jenkins Agent (macOS)
設定門檻 低,5 分鐘內註冊完成 中,需要先搭 Jenkins master(通常在 Linux/Docker)
與程式碼儲存庫整合 GitHub 原生,PR 檢查狀態自動回寫 需要 GitHub Branch Source 外掛設定
多平台混合 同一 workflow 可混用 macOS / Linux / Windows 需要 Label 路由,設定相對繁瑣
流水線視覺化 GitHub Actions 原生 UI,直觀 Blue Ocean 外掛效果好,但需自行維護
機密管理 GitHub Secrets + OIDC Jenkins Credentials,可對接 Vault
離線/內網建置 不支援(需能存取 GitHub) 支援,適合強合規場景
硬體需求 需要一台線上 macOS 機器(Runner 主機) 需要一台線上 macOS 機器(Jenkins Agent 主機)

從遷移角度來看,如果原先是 Jenkins 流水線,把 sh 'xcodebuild ...' 步驟遷移到 GitHub Actions YAML 的 run: 欄位幾乎是一比一對應的。最大的差異在於觸發條件語法併發控制:Jenkins 的 when { branch ... } 對應 GitHub Actions 的 on: push: branches:;Jenkins 的 lockable-resources 外掛功能對應 GitHub Actions 的 concurrency: 組語法。

兩套方案的共同前提是:都需要一臺常駐線上、固定 macOS 版本的物理 Mac 作為 Agent/Runner 主機。這臺機器是整條流水線穩定性的基礎——它發熱過載或版本漂移,CI 就會不可預期地失敗。

很多中小團隊在這裡遇到的實際困境不是方案選擇,而是:沒有一臺能長期跑 CI 的物理 Mac。本機辦公用的 MacBook 記憶體往往只有 8 GB,大專案編譯時散熱降頻明顯,在持續 CI 負載下體驗很差;而新採購一臺 Mac mini 的成本加上場地、電力與維護開支,前期投入也不低。

雲端獨享 Mac:沒有專屬裸機時的固定建置方案

針對上面這個困境,按需租賃獨享雲端 Mac 是一個值得認真評估的選項——既不需要前期硬體投入,又能獲得一台專屬的、固定環境的 macOS 建置節點。

以本文使用的 VPSRox Mac mini M4 節點為例:16 GB 統一記憶體 + 256 GB NVMe SSD 的標準設定,1 Gbps 獨享頻寬,單節點按天起租 $21.8,付款後 1–5 分鐘自動交付,無合同鎖定。

從 CI/CD 的角度,它相當於你租賃了一台永遠線上、獨享算力的 Mac mini,具體能做的事情與本機 Mac沒有差別:

  • 安裝任意版本 Xcode(透過 xcodes 工具管理),完全鎖定建置環境
  • 註冊為 GitHub Actions 自託管 Runner 或 Jenkins Agent,本文所有步驟均可直接套用
  • 利用 M4 的 10 核 CPU 和 38 TOPS Neural Engine,對含 Core ML 模型編譯的專案有更好的表現
  • 透過 Thunderbolt 5 並聯擴充,將多臺 Mac mini 組成私有建置叢集(適合大型團隊)

與 GitHub 託管 Runner 相比:按天租賃的成本在高頻建置場景下(如每天觸發超過 30 次建置)往往低於 macOS Runner 超額計費;與自購 Mac mini 相比:無需前期採購預算、可按專案階段調整租期(衝刺期加機、上線後減機)。

全球五個節點(新加坡、日本東京、韓國首爾、中國香港、美國東部)可以選擇距離團隊最近的位置,降低 git fetch 和依賴下載的延遲,進一步縮短整體建置時長。

一臺機器多用:CI/CD + 遠端開發桌面

雲端 Mac 節點並不只用來跑 CI。你可以同時把它作為遠端開發桌面(透過瀏覽器 VNC 或 SSH),在沒有本機 Mac 的情況下進行 Xcode 除錯、Instruments 分析、模擬器測試——這讓單臺節點的利用率大幅提升,攤薄了按日計費的單位成本。

裸機獨享 · 1–5 分鐘交付

把這套 CI/CD 流水線跑起來

文中所有步驟在 VPSRox 獨享 Mac mini M4 節點上均已驗證可用。開通節點 → SSH 進去 → 按本文步驟操作,從零到 TestFlight 自動上傳,最快當天完成。按天起租,不需要籤合同。

標準設定
晶片Apple M4 · 38 TOPS
CPU10 核(4P + 6E)
記憶體16 GB 統一記憶體
網路1 Gbps 獨享頻寬
SLA99.9% 可用性
交付1–5 分鐘自動開通