オファー

Flutter iOS CIがクラウドMacで遅い理由:3層キャッシュ失効が根本原因

CI Flutter · iOS ビルド
2026-06-04 約15分

本稿の一本筋:Flutter iOS CI はキャッシュ失効の伝播問題。主矛盾は CocoaPods(ネットワーク+解決、上限の約70%)、次が DerivedData§0 と §0.1Macレンタル や GitHub Actions macOS runner 上の修正順が決まります。

30秒で読む(優先順)

  1. 主矛盾: Pods(ネット+解決)> DerivedData > Flutter cache — 先にPods。
  2. 単一因果: 「三層とも遅い」ではなく上流キャッシュミスが下流を再実行させる(§1)。
  3. 唯一の原則(§0):キャッシュは lockfile/SDK に紐づけ、日付にしない。
  4. 忙しい場合: §0+§0.1+§4 CocoaPods;ホットが遅ければ§5
  5. §7 Pipelinerestore順が key 文字列より重要。
クラウドMac上のFlutter iOS CIビルドとキャッシュ調整
表紙は示意。Benchmark条件は下表参照。

0. キャッシュ設計の唯一の原則

CocoaPods・DerivedData・PUB_CACHEの前に、全 key が従う原則:

CIキャッシュは「ファイル保存」ではなく「ビルド确定性の束ね」である。

  • pubspec.lock / Podfile.lock 変更 → miss → 正常
  • Xcode/Flutter SDK アップ → miss → 正常
  • Job時刻・日付・ランダム suffix だけで miss → 不可

違反の典型:毎日コールドスタート——「日付」や全消しで管理し、lockfile+版で管理していない。

0.1 Flutter iOS CI の真のボトルネック順

以降の章は同一判断に従う——三層は同等ではない

Time ceiling (optimize in this order):
  CocoaPods (network + resolve)  🔥🔥🔥  often ~70% of ceiling
        ↓
  DerivedData (compile increment) 🔥🔥    hot builds → ~6 min tier
        ↓
  Flutter cache (toolchain)       🔥      mainly first-run pub / engine

総判断(引用可): 遅さは三問題の並列ではなく、Podsが上限を先に決める。Pods未命中時、DerivedDataだけでは下流が空転。

読む順: 10分→§0+本节+§4;cold~15分でhot遅い→§5;§6はイメージ焼成と初回pub。

1. 単一因果:キャッシュ失効がIPAまで伝播する仕組み

三独立系ではなく一連の伝播連鎖。切れ目は上流か下流か。

flutter build ipaRunning pod install...で止まるなら§0.1:Podsキャッシュとネットを先に。区間計測:pub getpod installxcodebuild

1.1 伝播連鎖(起点→末端)

因果方向で読む(§0.1と一致):

  [start · primary]  CocoaPods miss / slow private specs
           │  Pod tree rebuild, Specs fetch
           ▼
  [propagate]        DerivedData forced cold (full xcodebuild)
           │
           ▼
  [end]              Slow IPA (often blamed on Flutter/Dart)

逆も:pubspec.lock変更→プラグイン→pod install再計算→DerivedData失効。restoreはPub先だが投入時間はPods→DerivedData→Flutter。

引用: 「Flutter CI遅い」の約90%は上流失効。DerivedDataだけ消してもPods命中後はコールドに見える——伝播損失。

公式: Flutter iOS deployment · CocoaPods environment · Xcode Build Settings.

2. Benchmark:§0.1の重みを検証(参考)

表はPods先行・DerivedData次の投資順の証拠。kvmboot典型(中規模Flutter・Pod約40・Release IPA・クラウドMac M4)。同一commitで再計測。

段階 重み cold hot 説明
キャッシュなし ~28 min ~28 min 連鎖全断;hot≈cold
Pods+Specs cacheのみ 🔥🔥🔥 ~19 min ~12 min 最大単発短縮(28→19);最優先
+ DerivedData cache 🔥🔥 ~11 min ~6 min ホットの要;Pods命中後
+ Flutter pub/エンジン 🔥 ~10 min ~5 min 周辺;初回pub短縮

cold>20分ならDerivedDataを触らず§4へ。cold~11分でhot>12分なら§5のrestoreとDERIVED_DATA_PATH

自測: Pipelineに/usr/bin/time -lまたはstep summaryで4行——pub、pod、xcodebuild、総wall。比較時はcommitとFLUTTER_VERSION固定。

3. デュアルAgent同機:リソース境界のみ

2026-06-03の関連記事 クラウドMac デュアルAI Agent:Claude Code + Codex 分離(2026-06-03)二つのCLIが同一worktreeを壊さない話。同じMacレンタル機で夜間Flutter iOS CIを重ねるときの境界:

  • キャッシュ根の分離: CI専用DERIVED_DATA_PATH、AgentのDerivedDataと共有しない。
  • スケジュール分離: xcodebuildとAgent索引の並列はswap飽和——16GBは昼Agent・夜CI。
  • 交叉操作禁止: AgentがCIのios/Podspod updateしない。

4. CocoaPods CI 最適化 🔥🔥🔥

主矛盾層。 Flutter iOS build slowの約70%はpod install。ここで28分→19分——未整備ならDerivedDataは救えない。§0:Podfile.lock+CP版。

4.1 環境変数と永続パス

export CP_HOME_DIR="${CP_HOME_DIR:-$HOME/.cocoapods}"
export COCOAPODS_PARALLEL_CODE_DOWNLOAD=true
# persist: ~/.cocoapods/repos + ~/Library/Caches/CocoaPods

私庫/バイナリPod:GitHub Actions macOS runnerまたはセルフホストクラウドMacをGitと同リージョン。.netrcまたはsecret。詳細: BitriseとセルフホストMac Runnerの意思決定.

4.2 Pods/ ツリー:cacheかコミットか

  • 一般的: ios/Podsをcache、key=hashFiles('ios/Podfile.lock')
  • コンプラ重視: Pods/コミット、CIはpod install --deployment

無差別pod updateはlock漂移と§0破壊。

暗黙podなら先にcd ios && pod install --verboseflutter build ipa。ネットか解析かを切り分ける。

5. Xcode DerivedData cache 🔥🔥

伝播段。 Pods命中後、hotが~6分に入るか決まる。§0:Xcode+Flutter+lockfiles;版跨ぎ再利用禁止。§4が遅いなら優先ではない。

export DERIVED_DATA_PATH="/var/ci/derived/flutter-${FLUTTER_VERSION}-xcode-${XCODE_VERSION}"
xcodebuild -workspace ios/Runner.xcworkspace -scheme Runner \
  -configuration Release -derivedDataPath "$DERIVED_DATA_PATH" \
  -destination 'generic/platform=iOS' build

Tier掃除——毎Job全消し禁止。容量/inode: クラウドMac Runnerのディスク・inode治理.

6. Flutter ツールチェーン cache 🔥

周辺層。 PUB_CACHEとSDK bin/cacheは初回DL短縮が主。1–2分程度。lock変更は上流pod再計算(§1)。

Job間でbuild/iosを優先restoreしない。iOSは裸macOS/クラウドMac( Docker排障の役割分担.

7. GitHub Actions macOS runner:§0.1重みのPipeline

§4–§6をYAML化。restore順(Pub→Pods→DerivedData)は連鎖用;key調整の労力はPods→DerivedData。

ブロック パス key要素(§0)
1pub 🔥PUB_CACHEpubspec.lock + Flutter SDK
2–3pods 🔥🔥🔥~/.cocoapods + ios/PodsPodfile.lock + CP version
4deriveddata 🔥🔥$DERIVED_DATA_PATHXcode + Flutter + lockfiles
# .github/workflows/flutter-ios.yml (structure; macos-latest or self-hosted cloud Mac)
jobs:
  build-ios:
    runs-on: macos-14   # or runs-on: [self-hosted, cloud-mac]
    steps:
      - uses: actions/checkout@v4

      # ── Restore (order: Pub → Pods Specs → Pods tree → DerivedData) ──
      - name: Restore Pub Cache
        uses: actions/cache/restore@v4
        with:
          path: ${{ env.PUB_CACHE }}
          key: pub-${{ hashFiles('pubspec.lock') }}-flutter-${{ env.FLUTTER_VERSION }}

      - name: Restore CocoaPods Specs
        uses: actions/cache/restore@v4
        with:
          path: |
            ~/.cocoapods
            ~/Library/Caches/CocoaPods
          key: pods-specs-${{ env.COCOAPODS_VERSION }}

      - name: Restore Pods Tree
        uses: actions/cache/restore@v4
        with:
          path: ios/Pods
          key: pods-tree-${{ hashFiles('ios/Podfile.lock') }}

      - name: Restore DerivedData
        uses: actions/cache/restore@v4
        with:
          path: ${{ env.DERIVED_DATA_PATH }}
          key: dd-${{ env.XCODE_VERSION }}-${{ env.FLUTTER_VERSION }}-${{ hashFiles('pubspec.lock', 'ios/Podfile.lock') }}

      # ── Build ──
      - name: Flutter Pub Get
        run: flutter pub get

      - name: Pod Install
        run: cd ios && pod install --verbose

      - name: Flutter Build IPA
        run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist

      # ── Save (mirror restore; save costly layers even on failure) ──
      - name: Save Pub Cache
        uses: actions/cache/save@v4
        if: always()
        with:
          path: ${{ env.PUB_CACHE }}
          key: pub-${{ hashFiles('pubspec.lock') }}-flutter-${{ env.FLUTTER_VERSION }}

      - name: Save Pods + DerivedData
        uses: actions/cache/save@v4
        if: always()
        with:
          path: |
            ~/.cocoapods
            ~/Library/Caches/CocoaPods
            ios/Pods
            ${{ env.DERIVED_DATA_PATH }}
          key: dd-${{ env.XCODE_VERSION }}-${{ env.FLUTTER_VERSION }}-${{ hashFiles('pubspec.lock', 'ios/Podfile.lock') }}

ビルド後の署名: クラウドMac codesign / Notarization — 本文では省略。

落とし穴:restore前にpod install失敗でsave省略。高コスト層はif: always()

8. ディスクとinode(工程要点)

三層cacheで256GBが数日で60%——inodeが容量より先に枯れることも。CIユーザー80%/85%アラート。月次RunnerはTier-1掃除。発版冲刺の日払いは期限終了で破棄可: 発版冲刺の一時構築マトリクス.

du -sh ~/Library/Developer/Xcode/DerivedData ~/.cocoapods "${PUB_CACHE:-$HOME/.pub-cache}" 2>/dev/null
df -h .; df -i .

9. 受け入れと切り分け(§0.1順)

①🔥🔥🔥 lock不変でpod install<90s、cold~19分。②🔥🔥 同一commitでhot P95がcold比40%短縮、~6分。③🔥 初回pub省略可否。①を飛ばしてDerivedData調整しない。

症状 高確率原因 最初の手
hot≈cold、20分超 毎Job DerivedData削除/パス未固定 -derivedDataPathとrestore確認
pod段だけ遅い Specs CDN/私庫認証 CP_HOME_DIR.netrc、リージョン
偶発リンクエラー Xcode版跨ぎDerivedData再利用 keyにXcode版、旧dir廃棄
SSHが重い ディスク満杯/inode枯渇 df -h; df -i+ディスク治理

10. FAQ

10.1 クラウドMac/GitHub ActionsでFlutter iOS CIが特に遅い?

多くはCocoaPodsキャッシュミスが伝播でDerivedDataを冷やす。§0.1順にPods→DerivedData。Dartを疑う前にPods。

10.2 CocoaPodsとDerivedDataどちら先?

CocoaPods先。 Podsのみで28→19分;DerivedDataはPods後のhot(12→6分)。

10.3 DerivedDataだけcacheで足りる?

不足。伝播段の最適化;pod installがcoldを占有しうる。§0.1と§2🔥🔥🔥。

10.4 GitHub Actions macOS runnerのFlutter cache

§7:restore Pub→Pods→DerivedData;saveはif: always();keyはlock/SDK(§0)。

10.5 デュアルAgent同機との併用

2026-06-03のデュアルAgent記事はworktree分離;本稿はCIキャッシュ根。別DERIVED_DATA_PATH(§3)。

11. 結論

Flutter iOS CIは三系統の個別修理ではなくキャッシュ失効伝播連鎖CocoaPods(🔥🔥🔥)→DerivedData(🔥🔥)→Flutter(🔥)。§4で~19分、§5でhot安定、§6–§7で仕上げ。Podsが速さの上限、DerivedDataがhotを安定させる。

48時間でiOS CI cache strategyを検証

すでに クラウドMac デュアルAI Agent:Claude Code + Codex 分離(2026-06-03) を同じMacレンタル機で通過済みなら、夜間Flutter Pipelineを追加:1日目cold、2日目§7の4層restore、3日目§2でhot~6分。podが遅ければ私庫ネットを先に。

SSH開通・M4:Macレンタル開通検収 · スペックと期間 · 構成プラン