結論:WWDC 26 後に CI で Xcode 26 beta を試すなら、main の workflow を macos-latest に差し替えて祈るのではなく、リモート Mac 上で Runner を 2 レーンに分離するのが正解です。本番は Xcode 16 安定版に固定し、beta は専用 label と専用 DerivedData ディレクトリで走らせます。
3 つの鉄則:① 本番 job では sudo xcode-select を禁止;② beta と本番の -derivedDataPath は物理的に分離;③ iOS 27 Simulator runtime は beta Runner に一度だけプリインストールし、job ごとのダウンロードをやめる。
本文の範囲:「WWDC 後に CI が遅くなる理由」(WWDC26 iOS CI 遅延の解説)は扱いません。48 時間で実行できる Runbook だけを書きます。
6 月 10 日の記事で触れた通り、WWDC 後の iOS CI の揺れはキャッシュ消去・SDK の肥大化・ホスト Runner の混雑が重なった結果です。1 週間経ち、Slack の質問は実務寄りに変わりました——「beta はどの Mac に入れる?」「1 台で 2 つの Xcode は無理?」「main が赤くなったら誰の責任?」。答えはシンプルです:リモート Mac mini を self-hosted runner にして、beta 検証と App Store リリースを完全に分ける。 self-hosted 未導入なら先に iOS CI 高速化ガイド を。ノード別の月額比較は 6 拠点 Runner TCO を参照してください。
1)よくある誤解:1 台 Mac で混在 ≠ 節約
WWDC 直後に多い判断ミスは、CI 用 Mac が 1 台だけの環境に Xcode 16 と 26 beta を両方入れ、workflow の xcode-select -s で切り替えるパターンです。月額 1 台分に見えて、実際のコストはこう跳ね上がります:
- 競合:並列 job 実行時、後から走る
xcode-selectがグローバルパスを書き換え、本番 Archive が beta コンパイラを使う事故が起きる。 - キャッシュ汚染:
~/Library/Developer/Xcode/DerivedDataを共有すると Swift module の版が混ざり、ローカル再現不能な「幽霊エラー」が CI だけで赤くなる。 - ディスク争奪:Xcode 2 セット + Simulator runtime 2 セットで 80GB 超は普通。16GB メモリ機は beta のフルビルド中に swap し、全 job が遅くなる。
2)デュアル Runner 構成:label プールと workflow 分流
最小構成は次のとおり——リモート Mac mini M4 を 2 台(または本番 1 台 + beta 用の日割りトライアル機):
| Runner | GitHub Label | Xcode 版 | 担当 job |
|---|---|---|---|
| 本番機 A | self-hosted, macos, ios-prod | Xcode 16.4(固定) | main Archive、TestFlight、リリース tag |
| Beta 機 B | self-hosted, macos, xcode26-beta | Xcode 26 beta | ios-27-* ブランチ、nightly 適応、API 探索 |
workflow 側は runs-on でハード分流。beta ブランチが ios-prod label を触るのは禁止:
name: iOS Production CI
on:
push:
branches: [main, release/*]
jobs:
archive:
runs-on: [self-hosted, macos, ios-prod]
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
DERIVED_DATA: /var/ci/deriveddata/prod
steps:
- uses: actions/checkout@v4
- name: Build & Archive
run: |
xcodebuild -scheme MyApp -configuration Release \
-derivedDataPath "$DERIVED_DATA" \
-archivePath build/MyApp.xcarchive archive
name: iOS 27 Beta Adapter
on:
push:
branches: [ios-27-*, feature/siri-ai-*]
schedule:
- cron: '0 2 * * *' # nightly、merge をブロックしない
jobs:
beta-build:
runs-on: [self-hosted, macos, xcode26-beta]
continue-on-error: true # beta の赤は main を止めない
env:
DEVELOPER_DIR: /Applications/Xcode_26_beta.app/Contents/Developer
DERIVED_DATA: /var/ci/deriveddata/beta
steps:
- uses: actions/checkout@v4
- run: xcodebuild -scheme MyApp -sdk iphonesimulator build \
-derivedDataPath "$DERIVED_DATA"
Runner 登録と label 命名は GitHub self-hosted runner ドキュメント を参照。label は契約——誰が label を変えたかで責任が決まります。
3)リモート Mac への Xcode 26 beta インストールと共存
ベアメタルリモート Macなら、2 つの Xcode を /Applications に並べて置き、ディレクトリ名で版を区別できます。beta が安定版を上書きしないように:
- Apple Developer Downloads から Xcode 26 beta(.xip)を取得し、SSH でリモート Mac にログインして
/Applications/Xcode_26_beta.appに展開。 - 本番機は
DEVELOPER_DIR環境変数のみ受け付け、workflow に絶対パスを書く。job 内のsudo xcode-select -switchは禁止。 - 初回インストール後、
xcodebuild -runFirstLaunchとライセンス同意を手動で 1 回実行——これを毎 CI job に入れない。 - iOS 27 Simulator runtime は beta 機にプリインストール:Xcode → Settings → Platforms で一括インストール;workflow から
-downloadPlatformステップを削除。
ローカル開発でも複数 Xcode を使うチームは、Xcode ビルドが遅い問題 のハイブリッド運用が参考になります。手元で Swift を書き、重いコンパイルはクラウド M4 に任せる——beta 適応も同じで、16GB ノートに beta のインデックスとフルビルドを載せないこと。
4)DerivedData / Pods / SPM の 3 系統キャッシュ分離
WWDC シーズンにキャッシュが死ぬ根本原因はコンパイラと module 形式の変更であって、「キャッシュが壊れた」わけではありません。分離方針:
| キャッシュ種別 | 本番パス | Beta パス | 備考 |
|---|---|---|---|
| DerivedData | /var/ci/deriveddata/prod | /var/ci/deriveddata/beta | workflow で -derivedDataPath 固定 |
| CocoaPods | /var/ci/cocoapods/prod | /var/ci/cocoapods/beta | CP_HOME_DIR または --deployment |
| SPM | /var/ci/spm/prod | /var/ci/spm/beta | clonedSourcePackagesDirPath |
| ModuleCache | DerivedData 親ディレクトリに従属 | 独立 beta ツリー | デフォルト ~/Library 共有禁止 |
actions/cache は WWDC シーズンには足りないことが多い:Xcode メジャー切替で cache key が全滅し、数 GB の DerivedData をアップロードするネットワーク時間はローカルディスクより損になりがち。self-hosted の価値はディスクが job を跨いで生き残ること——2 回目の beta build で初めて 20 分超から 10 分台に戻ります。Flutter の 3 キャッシュ詳細は Flutter iOS CI、署名と TestFlight は iOS CI 高速化ガイド の Signing 章を参照。
sudo mkdir -p /var/ci/{deriveddata,cocoapods,spm}/{prod,beta}
sudo chown -R $(whoami) /var/ci
# ディスク監視:DerivedData 膨張時は lane 単位でクリーン、ディスク全体 rm は避ける
du -sh /var/ci/deriveddata/*
5)典型トラブル:beta クラッシュ、SDK 肥大、キーチェーン混線
現場でよく見る事故と、その予防策:
- beta の赤が merge を止める:beta workflow に
continue-on-error: trueを設定し、required status check にしない。beta が不安定なのは想定内で、エンジニアの怠慢ではない。 - 本番が beta SDK を使う:Archive ログで
DTXcodeとDEVELOPER_DIRを確認;prod job 冒頭でtest "$DEVELOPER_DIR" = "/Applications/Xcode_16.4.app/Contents/Developer"をアサート。 - キーチェーン / Match 証明書の混線:2 台の Runner はそれぞれ独立 login keychain;Fastlane match の
git_urlは共有可能だが、証書インポート用 keychain パスワードは機ごとに分離。Apple の Xcode サポートマトリクス も参照。 - ディスク満杯:beta DerivedData + 2 セット Simulator runtime の増殖は速い;beta 機は 512GB SSD 推奨、
betaツリーで 14 日以上未アクセスの subfolder を週次 cron で削除。 - 並列 Archive で OOM:M4 16GB で Archive 2 本同時は swap 地獄;本番機 concurrency は 1、beta 機は 1–2 で調整。
6)サンプル:Runner 分離前後の同一リポ P50(SLA ではない)
Swift/UIKit + CocoaPods の中規模プロジェクト、main 本番 job の WWDC 後 1 週目比較——未分離(全員 beta 昇格)vs デュアル Runner(本番は Xcode 16 のまま):
| 指標 | 未分離(main を beta 化) | デュアル Runner 分離 |
|---|---|---|
| main Archive P50 | ~42 min | ~11 min(WWDC 前と同等) |
| beta 適応 job P50 | (prod と同一台を奪い合い) | ~22 min(初週フル、2 週目 ~12 min) |
| main の beta 誤爆失敗率 | 高(コンパイラ/キャッシュ混在) | ほぼ 0 |
| 必要 Mac 台数 | 1 台(一見お得) | M4 2 台、または本番 1 + beta 日割り |
数値はリポジトリ依存。自社 workflow で 48 時間 A/B を取ってください。本質は「どれだけ速くなるか」ではなく、main が beta の代金を払わないことです。
7)48 時間ロールアウトチェックリスト
- Day 0 午前:本番 Xcode 版をロックし、prod workflow に
DEVELOPER_DIRを固定;main への「Xcode 26 昇格」CI 変更のマージを禁止。 - Day 0 午後:リモート Mac mini(または 48h 日割り)を
xcode26-betaRunner として登録;Xcode 26 beta + iOS 27 runtime をインストール。 - Day 1:
/var/ci/...キャッシュディレクトリを作成;beta workflow の初回フルビルドを通す;prod job への影響がないことを確認。 - Day 2:beta workflow を nightly +
ios-27-*ブランチトリガーに変更;GitHub branch protection から beta check の required を外す。 - 受け入れ:main Archive を 3 連続 < 15 min(プロジェクト次第);beta job 失敗で Slack @channel しない。
2 台目の月額が妥当か迷うなら、日割り beta 機で iOS 27 適応 spike を終えてから本契約を判断——TCO テンプレは MacBook Pro vs クラウド Mac 判断。ツールチェーンが macOS 独占な理由は Xcode ツールチェーン独占の解説 を参照。
8)よくある質問(15 件)
1. 1 台のリモート Mac に 2 つの Xcode は入る? 入ります。別 .app 名で共存;CI では DEVELOPER_DIR を指定し、グローバル xcode-select に頼らない。
2. beta Runner は 24GB 必須? 単一 scheme の中規模なら 16GB M4 で足りることが多い;複数 target 並列 Archive は 24GB 推奨。
3. 本番機でたまに beta job を回せる? 非推奨。時間分割でも DerivedData は最低限分離;できれば物理分離。
4. ホスト macos-latest が beta Runner の代わりになる? ならない。ephemeral ディスクは毎回コールドスタート、WWDC シーズンの bootstrap だけ 15 分超、永続キャッシュなし。
5. actions/cache で足りる? メジャー版切替で key 無効;大 DerivedData の up/down はローカルディスクに劣る。
6. beta 不安定で CI が真っ赤? 想定内;beta lane は optional にし、main merge をブロックしない。
7. prod が beta コンパイラを使っていない確認方法? Archive ログの DTXcode、または job 冒頭で DEVELOPER_DIR をアサート。
8. Simulator runtime を毎 job ダウンロード? しない。beta 機に 1 回プリインストール、workflow から download 削除。
9. 2 台の Runner は別リージョンでも OK? OK。本番はチームに近いノード;beta は prod と同区画の方が ops が楽。
10. OpenClaw で beta Runner を管理できる? webhook トリガーは可能;Gateway 詳細は割愛。CI 登録は OpenClaw CI Runner FAQ。
11. 日割り beta 機で足りる? 適応 spike なら十分;nightly を長期運用なら月額の方が安い。
12. DerivedData はどのサイズでクリーン? lane 単位 40GB 超なら scheme 別クリーン;本番 lane は慎重に、beta を優先削除。
13. Swift 6 並行チェックが厳しくなったら? beta lane だけ strict フラグを有効化、prod は現状維持で段階移行。
14. WWDC 記事との関係は? あちらは「なぜ遅いか」、こちらは「Runner をどう分けるか」。
15. 48h 以内に 2 台必須? main が beta に触れないなら prod 1 台で可;beta 適応は日割り機が来てから lane を開く。
Beta は試せ、本番は絶対に安定させろ
WWDC 後いちばんコスパが良い構成は、月額本番 Runner 1 台 + オンデマンド beta 機 です。本番 Mac mini M4 を 7×24 で TestFlight ペースを守り、beta は日割りで iOS 27 適応を試し、結果を見て本契約を判断。Nuvcloud のベアメタルはディスク独占、DerivedData が job を跨いで生存;M4 16GB で大半の iOS Archive は十分、24GB は複数 scheme 並列向け。低消費電力・ファンレスで、無人 CI に向きます。
WWDC 後にXcode 26 beta CI を組みたいが、main を実験場にしたくないなら、 Nuvcloud クラウド Mac mini M4 が最も安い分離の起点です—— プランを今すぐ確認 、48 時間で beta / 本番 Runner を切り分けましょう。