なぜセルフホスト 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 分に換算、やや密集したイテレーションサイクルで超過課金。
対照的に、自前 Mac に接続したセルフホスト Runner は上記 3 点を完全回避:Runner は常時オンライン、Xcode バージョンは自行管理、実行時間は課金なし。唯一のハードルは常時稼働 macOS マシンが必要——本文が解決する問題。
準備:クラウド Mac ノードの開通と基本環境
iOS CI/CD 接続前に、SSH 接続可能で 24 時間オンラインの macOS マシンが必要。本文は VPSRox 専用 Mac mini M4 ノードを例に(物理 Mac mini / Mac Studio も適用可)。VPSRox コンソール納品後 5 分以内に以下の手順を完了可能。
SSH 接続と Xcode Command Line Tools
-
01
SSH 鍵認証を設定
コンソールでパブリック IP をコピーし、ローカルで実行:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@<ノードIP>完了後、パスワードログインを無効化(
PasswordAuthentication no)し、ブルートフォース攻撃を防止。 -
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 -
03
Homebrew をインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"インストール後、
/opt/homebrew/binを PATH に追加し、~/.zprofileに書き込む。 -
04
Xcode ビルドチェーンを検証
xcodebuild -versionXcode バージョンと Build Version の出力を確認。後続 YAML で
XCODE_VERSION環境変数でバージョンを固定可能。
brew install xcodesorg/made/xcodes で xcodes をインストールし、Xcode バージョンを素早く切替。xcode-select パス手動変更不要。CI ワークフローで xcodes select 16.2 によりバージョンを厳密固定、漂移を防止。
GitHub Actions セルフホスト Runner のインストールと登録
GitHub 提供のセルフホスト Runner インストーラーは Mac 上でポーリングデーモンを実行し、workflow トリガー時に job をローカルで実行。Mac からポートを公開する必要はない。
GitHub リポジトリで登録トークンを取得
対象リポジトリ → Settings → Actions → Runners → New self-hosted runner、macOS プラットフォーム選択、ページのダウンロードコマンドと登録トークンをコピー(Token 有効期限 1 時間)。
-
01
Runner をダウンロードして解凍
mkdir actions-runner && cd actions-runnercurl -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.gztar xzf ./actions-runner-osx-arm64-2.319.1.tar.gzosx-arm64パッケージ(Apple Silicon)を選択。バージョンは GitHub ページの表示に従う。 -
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で使用し、このマシンへ正確にルーティング可能。 -
03
launchd サービスとしてインストール(起動時自動起動)
./svc.sh install./svc.sh startlaunchd はユーザーログイン後に 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 は公開リポジトリで fork PR によりトリガーされる可能性があり、悪意コードが Mac 上で実行される。推奨:①プライベートリポジトリまたは Organization 範囲のみ有効化;②Runner をシステムアカウント(非管理者)で実行、権限を厳格制限;③~/.bash_history と ~/actions-runner/_diag/ ログを定期監査。
Xcode ビルド環境と依存関係管理の設定
クリーンな iOS ビルドには通常 3 点が必要:依存取得、コンパイルキャッシュ、派生データパス分離。
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') }}
DerivedData パス分離
同一 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 フローで最もエラーが起きやすい工程。GUI なし CI 環境では、デフォルトの macOS Keychain の挙動が通常の開発環境と異なり、キーチェーンを明示的に管理する必要がある。
2 つの主流署名方式の比較
| 方式 | 適用シーン | メリット | デメリット |
|---|---|---|---|
| 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_PASSWORD、P12_PASSWORD など機密変数は GitHub Repository Secrets に保存し、${{ secrets.KEYCHAIN_PASSWORD }} で参照。YAML にハードコード厳禁。
Archive を実行して IPA をエクスポート
完全な Archive → Export フローは 2 段階: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 ステップの重要設定ファイル。method(app-store / ad-hoc)、teamID、signingStyle フィールドが必要。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 Access → Integrations → App Store Connect API で API Key 作成、.p8 ファイルをダウンロード、Key ID と Issuer ID を記録。
GitHub Secrets に以下の変数を設定:
ASC_API_KEY_ID:API Key の IDASC_API_ISSUER_ID:Issuer IDASC_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
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 にコードをプッシュできない場合、Mac 上の Jenkins は一般的な代替案。両者の核心的な差はいくつかの観点に現れる。
| 観点 | 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: フィールドへ移行はほぼ 1 対 1 対応。最大の差はトリガー条件構文と並行制御: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 を借りるのと同等。できることはローカル専用物理サーバーと変わらない:
- 任意バージョン Xcode をインストール(xcodes ツールで管理)、ビルド環境を完全固定
- GitHub Actions セルフホスト Runner または Jenkins Agent として登録、本文の全手順を直接適用可能
- M4 の 10 コア CPU と 38 TOPS Neural Engine により、Core ML モデルコンパイルを含むプロジェクトでより良い性能
- Thunderbolt 5 並列拡張で複数 Mac mini をプライベートビルドクラスタに(大規模チーム向け)
GitHub ホスト Runner との比較:日単位レンタルは高頻度ビルド(1 日 30 回超)で macOS Runner 超過課金より安いことが多い。Mac mini 自購との比較:初期調達予算不要、プロジェクト段階に応じてレンタル期間調整(スプリント期に増機、リリース後に減機)。
5 つのグローバルノード(シンガポール、日本東京、韓国ソウル、中国香港、米国東部)からチームに最も近い位置を選択し、git fetch と依存ダウンロードのレイテンシを低減、全体ビルド時間をさらに短縮。
クラウド Mac ノードは CI 専用ではない。ブラウザ VNC や SSH でリモート開発デスクトップとして同時利用可能。ローカル Mac なしで Xcode デバッグ、Instruments 分析、シミュレータテスト——単一ノードの利用率が大幅向上し、日単位課金の単位コストを分散。
この CI/CD パイプラインを稼働させる
本文の全ステップは VPSRox 専用 Mac mini M4 ノードで検証済み。ノード開通 → SSH 接続 → 本文の手順に従い、ゼロから TestFlight 自動アップロードまで、最短当日完了。日単位レンタル、契約不要。