オファー

xcodebuild が CI だとローカルの 2〜3 倍遅いのはなぜ?

CI Flutter iOS CI
2026-06-08 約 12 分

Flutter iOS の現場で再現しやすい手順に絞り、CI 遅延の実測・原因分解・改善順序をまとめました。

Flutter iOS CI のローカルと CI の時間差
ローカルでは問題ないのに、GitHub Actions だと xcodebuild 周辺が冷えた状態で毎回始まり、合計が 2〜3 倍に伸びやすい構図です。

最初に押さえるポイント

  1. 遅い原因は xcodebuild 単体ではなく、キャッシュと実行環境のライフサイクル差です。
  2. GitHub-hosted は毎回コールドスタート、自前Runner はウォーム状態を維持しやすいです。
  3. DerivedData と CocoaPods を持続化できると、待ち時間の主要部分を削れます。
  4. ディスク逼迫や署名準備は「コンパイル遅延」に見えるため、段階計測が必須です。
  5. Flutter iOS チームはまず Macレンタルで 48 時間の実測を取り、月額運用へ進むのが安全です。

ローカル Mac と GitHub Actions の差はどこで生まれるか

ローカルで計測するとき、多くのチームは次のような前提で比較しています。

  • ローカル: 持続 SSD + 温まった依存状態 / CI: 使い捨て VM + コールド依存。
  • ローカル: 同じ署名環境 / CI: 毎回 keychain ブートストラップ。
  • ローカル: 同一ホスト反復ビルド / CI: 分岐ごとのキャッシュミス。

一方 CI パイプラインでは、だいたい次のような運用になります。

  • 毎回クリーンなワークスペースに checkout する。
  • flutter build ipaxcodebuild archiveRelease で実行する。
  • ホステッド Runner ではキャッシュが残りにくく、毎回コールドスタートになる。

多くの比較は `xcodebuild archive` だけを見ますが、実際には checkout、pod install、keychain 解錠、DerivedData 失効が積み重なります。これが体感 2〜3 倍の正体です。

xcodebuild が CI で 2〜3 倍遅くなる 5 つの原因

GitHub Actions 利用中でも、自前Runnerへ移行中でも、Flutter iOS で繰り返し出る要因です。

  1. 1) 無状態 Runner が依存レイヤーを毎回リセット GitHub-hosted VM はジョブごとに新規生成されるため、SPM・Pods・モジュールキャッシュが安定して温まりません。actions/cache だけでは吸収し切れないケースが多いです。
  2. 2) DerivedData パスの揺れで増分ビルドが崩れる 一時パスを使うと、ローカルで再利用できる成果物が CI では無効扱いになり、再コンパイルが増えます。
  3. 3) Flutter プラグイン更新で Pods 解決がコールド化 プラグイン更新により lockfile が揺れると `pod install` が毎回重くなります。deployment モードで再現性を固定してください。
  4. 4) 署名と keychain 準備が直列オーバーヘッド 証明書インポート、keychain 作成・解錠は CI で毎回発生します。ローカルでは既存状態を活かせるため差が出ます。
  5. 5) ディスク/ inode 圧迫が静かにビルドを遅くする CPU だけ見ても不十分です。空き容量不足や inode 枯渇は、インデックス更新と書き込みを確実に遅くします。

ステージ別計測ログ(Flutter iOS 例)

まずこの形式で可視化し、原因を定量化してから環境移行を判断してください。

ステージ ローカル Mac GitHub Actions 典型要因
flutter pub get0m25s1m10spub キャッシュが十分温まっていない
pod install1m30s4m45sSpecs と plugin pod 解決がコールド
xcodebuild compile/archive6m40s12m20sDerivedData 非再利用
codesign/export0m55s2m00skeychain 準備を毎回実施
合計9m30s20m15s約 2.1 倍

xcodebuild を疑う前に

xcodebuild の前に pod install を見落としていないか確認してください。CI で CocoaPods が遅くなる典型原因は次の 3 つです。

  • Podfile.lock 未コミット — 依存解決が毎回不安定になる。
  • ios/Pods のキャッシュなし — ジョブ終了後に消える。
  • 不要な pod repo update — lockfile があるなら通常不要。

CI では pod install --deployment を使い、Pods を同一 SSD パスに保持します。セルフホスト Mac mini なら GitHub へのキャッシュ転送待ちを避けられます。

DerivedData / Pods / キャッシュの実務設計

Flutter iOS では DerivedData の絶対パス固定と Pods キャッシュの整流化が最も効くことが多いです。自前Runnerなら、CI を使い捨てから継続運用型へ変えられます。

GitHub-hosted 継続でもパスと lockfile を決め打ちにすれば改善します。移行可能なら、Macレンタルで検証後に自前Runner化するとローカルに近い挙動になります。

  • target ごとに固定 derivedDataPath を採用。
  • `pod install --deployment` で lockfile ドリフト抑制。
  • 空きディスクと inode を CI 指標として継続監視。

修正テンプレート: パス固定 + 依存解決の決定性

以下は過度な最適化を避けた現実的なベースです。まず再現性を優先します。

  1. workflow に concurrency を追加 — 同一ブランチの古いビルドをキャンセル。
  2. テストは Linux で実行 — Mac 時間を flutter test に使わない。
  3. Pods + DerivedData を永続化 — セルフホストは固定パス、ホステッドは細かい cache key。
  4. 配布する flavor だけビルド — 6 flavor スキャンは不要。
  5. Xcode / Flutter バージョンを固定 — CI 内の upgrade を避ける。
# .github/workflows/ios-ci.yml
jobs:
  ios:
    runs-on: [self-hosted, macOS, ARM64, flutter-ios]
    steps:
      - uses: actions/checkout@v4
      - name: Restore caches
        run: |
          mkdir -p "$HOME/Library/Caches/CocoaPods"
          mkdir -p "$HOME/.pub-cache"
      - name: Build with stable DerivedData path
        run: |
          flutter pub get
          cd ios
          xcodebuild             -workspace Runner.xcworkspace             -scheme Runner             -configuration Release             -destination 'generic/platform=iOS'             -derivedDataPath "$HOME/ci-derived/runner"             CODE_SIGNING_ALLOWED=NO
      - name: Keep pod install deterministic
        run: |
          cd ios
          pod install --deployment

この土台だけで 30〜50% 改善するチームは珍しくありません。次に自前Runnerプールで中央値を詰めていきます。

速度回帰のマージ前チェック

Flutter / Xcode 更新後に CI が遅くなったら毎回実施。

  • ☐ CI は Release / IPA、ローカル比較は Debug
  • ☐ Job で flutter clean や DerivedData 削除を実行
  • pod install ログで毎回大量 Installing
  • ☐ GitHub ホステッド Runner でローカル SSD キャッシュなし
  • ☐ 16GB マシンで複数 Job やシミュレータを同時実行
  • ☐ README 変更でも iOS フルビルドが走る
  • ☐ CI の Xcode バージョンがローカルと不一致

上 3 項目にチェックがあればキャッシュ優先。下 2 項目ならトリガー条件とマシン仕様を見直してください。

FAQ

GitHub Actions は常にローカルより遅いですか?

常にではありませんが、Flutter iOS ではキャッシュが温まらない構成だと遅くなりやすいです。自前Runnerで差を縮められます。

すぐ自前Runnerへ移行すべきですか?

まず計測です。冷スタート損失が継続しているなら 1 workflow を自前Runnerで A/B 比較してください。

高価なマシンが必須ですか?

必須ではありません。安定キャッシュ前提なら、Macレンタルの Apple Silicon で十分改善できます。

チーム説得の材料は?

ステージ別時間を待機コストに換算し、速度だけでなくリリース予測性の改善も示すのが有効です。

xcodebuild の CI 遅延を体系的に潰すなら、この順番が実務向きです。

安定した iOS CI を実機 Apple Silicon で

kvmboot のクラウド Mac なら Flutter iOS パイプライン向けに専有運用できます。まず Macレンタル(日次)で計測し、合えば月額の自前Runnerへ。

CI のばらつきを減らしてリリース速度を戻したいなら、 プランを見る