← 返回技術部落格

Flutter iOS CI/CD:在 Mac mini M4 上建置 IPA 與 CocoaPods 快取優化實戰

Flutter iOS CI/CD:Mac mini M4 上的 flutter build ipa 與 self-hosted Runner
典型分工:Windows/Linux 寫 Dart,Mac mini M4 Runner 產出 IPA。

這篇只談一件事:Flutter iOS CI/CD(主關鍵字:Flutter iOS build on Mac mini)——如何在 Mac mini M4 上穩定跑 flutter build ipa,並把 CocoaPods 快取self-hosted macOS Runner 與 Linux 端的 Android/測試 job 拆開。Flutter 專案真正的瓶頸往往不是 Dart,而是 iOS 建置鏈路的集中依賴pod installxcodebuild archive → 簽章 → 上傳 TestFlight,整條鏈只能落在 Mac 上。我們組是典型的跨平台分工:Android 在 Windows/Linux CI 編譯,Flutter iOS CI 固定在機房一台獨享 Mac mini。若你還在混原生 Xcode,可對照 告別 Xcode 卡頓;Runner 註冊與 TCO 見 自建 macOS Runner

1)為什麼 Flutter iOS CI 必須單獨佔一台 Mac

「一套程式碼」解決的是業務邏輯複用,不是建置平面複用。iOS 產物仍走完整 Xcode 鏈路,無法在 ubuntu-latest 上完成 Flutter iOS CI;GitHub 託管 macOS 分鐘貴、佇列飄,且每次 job 清快取時 flutter build ipa 耗時會回到 15–25 分鐘檔。把 Flutter iOS CI/CD 放到持久化環境的 Mac mini 上,核心價值是:鎖 Flutter/Xcode 版本三層快取可複用7×24 不合蓋睡眠——團隊裡 Android 與 iOS 發版節奏才能真的解耦。

邊界: flutter testbuild apk 留在 Linux;Flutter iOS CI 專機只跑 iOS 整合建置與簽章相關步驟。

2)Flutter iOS CI 雲端架構圖

下面是我們線上在用的 Flutter iOS CI/CD 拓撲:開發者不必本地有 Mac,iOS 包由 self-hosted Runner 產出。

Flutter iOS CI 雲端架構

開發者(Windows/Linux/Mac)
本地:Dart · Android · git push
GitHub Actions · ubuntu-latest
flutter test · build apk
Mac mini M4 · self-hosted Runner
labels: macos, flutter-ios
fvm flutter build ipa
pod install · xcodebuild archive
TestFlight/App Store Connect

Mac mini 上持久化快取層(Flutter iOS CI 提速關鍵)

  • ~/.pub-cache
  • CocoaPods cache
  • DerivedData

3)Flutter iOS CI 三種落地模式

模式本地/其他 CI雲端 M4 Mac適合
A. 只把 iOS 建置放雲上日常 flutter run、Android 打包build ipa、上傳商店混合主力機(Win/Linux + 手機真機)
B. 全遠端 Flutter(少見)僅編輯器 + git模擬器、flutter doctor 全綠無本地 Mac,能接受 VNC 延遲
C. 雲 Mac = iOS RunnerPR 觸發 Linux job標籤 macos 的 self-hosted Runner發版頻繁、要強快取

我們日常是 A + C:合併後 Linux 跑 flutter testFlutter iOS CI 僅在 tag/main 合併時由 Mac mini Runner 觸發。模式 B 適合短期無 Mac 接入,長期仍建議 Runner 化。

4)Flutter iOS CI 環境清單(Mac mini 一次性裝好)

  1. Xcode + CLT:ios/Podfile、Flutter 通道要求的最低版本對齊;裝完跑 sudo xcodebuild -license accept
  2. Flutter SDK:fvm 或固定目錄 ~/flutter,在倉庫寫 .fvmrc,避免同事與 CI 各用一版。
  3. CocoaPods: gem install cocoapods 或 Homebrew;大倉建議鎖 Podfile.lock 並提交。
  4. 簽章: 團隊用 Fastlane Match 或 Xcode 自動簽章;p12 與描述檔不要進 Git,放加密倉庫或 CI Secret。
  5. 網路: 節點盡量靠近 Git 遠端與 pub.dev/CDN;公司內網 Git 需 VPN 或專線,見 幫助中心 的出口說明。

第一次在新機器上跑 flutter doctor -v,把輸出貼進團隊 Wiki——以後排障先對照這份「黃金快照」。

5)CocoaPods 快取優化:Flutter iOS CI 提速核心(含 Before/After)

同一套中等體量 Flutter 倉(約 40+ plugins、Release IPA),我們在三種環境下各跑 3 次取中位數——Flutter iOS CI 的差異幾乎全在「快取是否留在 Mac mini 上」。

表:Flutter iOS CI 全量 build ipa 耗時(分鐘,同一倉庫 · 2026-05 實測)
環境首次建置(冷啟動)二次建置(快取命中)
本地 MacBook Air M2(8GB,合蓋/降頻)18–25 min12–15 min
GitHub 託管 macos-latest(無自訂快取)16–22 min14–18 min
Mac mini M4(16GB)· Flutter iOS CI + 三層快取15–20 min4–8 min

二次建置從 12+ 分鐘壓到個位數,靠的是架構圖裡那三層持久化——這也是我們把 Flutter iOS build on Mac mini 從「能編」升級到「能當 CI」的分水嶺。

  • PUB_CACHE~/.pub-cache Runner 環境變數寫死路徑,job 間複用 Dart 依賴。
  • CocoaPods cache + ios/Pods Podfile.lock 未變則跳過 pod install;變則只增量解析。
  • DerivedData: 固定 ~/DerivedData,除非升級 Xcode 大版本否則不 clean
量化結論: 若你們每週 ≥1 次 iOS 發版,Flutter iOS CI 在 Mac mini 上命中快取後,單次 pipeline 可節省約 8–12 分鐘;月 4 次發版、3 名開發者等待,就是一整 WORKDAY 量級的排隊時間。

6)Flutter iOS CI 命令流(SSH 或 Runner 腳本)

在倉庫根目錄,版本號請換成你專案鎖定的 Flutter:

cd ~/work/my_flutter_app
export PUB_CACHE=$HOME/.pub-cache
fvm flutter pub get
cd ios && pod install --repo-update && cd ..
fvm flutter build ipa --release \
  --export-options-plist=ios/ExportOptions.plist

在 GitHub Actions 裡,把上述步驟寫進 release-ios.ymlruns-on: [self-hosted, macos, flutter-ios] 即可。上傳 TestFlight 時 Mac mini 出口頻寬與 App Store Connect 路由,往往比開發者家裡 Wi‑Fi 更穩定。

7)GitHub Actions:Flutter iOS CI/CD workflow 拆分

建議拆成兩條 workflow(或 job matrix 裡用 if 分流):

  • test-android.yml ubuntu-latestflutter testbuild apk
  • release-ios.yml runs-on: [self-hosted, macos, flutter],只在 tag 或 main 合併時跑 build ipa + 上傳。

雲 Mac 同時跑 VNC 除錯和 Runner 時,給建置單獨系統使用者,避免「人工改檔 + 自動化檢出」互相踩。併發與磁碟規劃可參考 Runner TCO 一文。Windows 主力機同學的整體路徑,還可看 Windows + 雲端 Mac——邏輯與 Flutter 一致,只是本地沒有 Xcode 而已。

8)常見問題:Flutter iOS CI 為什麼卡住(how/fix)

這部分對應 Google 上最常見的 why/how/fix/error 搜尋——也是我們在團隊 Wiki 裡維護的排障入口。

pod install 要跑 10+ 分鐘,正常嗎?

Why: Runner 每次在臨時目錄 checkout,CocoaPods 下載快取未掛載,或 pod install --repo-update 被寫進預設腳本。
Fix: 固定 WORK 路徑;對比 Podfile.lock hash,未變則跳過;把 --repo-update 改成每週維護 job 手動跑。

xcodebuild archive 失敗/exit code 65

Why: Flutter 與 Xcode 版本不匹配、DerivedData 損壞、或某 native plugin 未相容目前 SDK。
Fix: CI 開頭跑 fvm flutter doctor -v 並歸檔日誌;刪 DerivedData 後重跑;對照 Xcode Release Notes 升級通道。

signing failed/描述檔不匹配

Why: 鑰匙圈未匯入 p12、bundle id 與描述檔不一致、Match 倉庫權限過期。
Fix: Mac mini Runner 用專用鑰匙圈;Fastlane Match 續期;必要時 VNC 點「始終信任」——Flutter iOS CI 裡這是最高頻的「非程式碼」失敗。

DerivedData 暴漲占滿磁碟

Why: 多分支並行 job、舊 Archives 未清、多 Flutter 版本共存。
Fix: cron 清 ~/Library/Developer/Xcode/Archives;Runner 併發設為 1–2;磁碟建議 ≥512GB 或定期擴容。

Flutter 版本不一致導致 plugin 編譯錯誤

Why: 開發者本地未用 fvm,CI 用了另一通道。
Fix: 倉庫提交 .fvmrc;CI 統一 fvm install && fvm flutter pub get;在 PR 模板加「是否變更 Flutter SDK」勾選項。

9)什麼時候該上專用 Mac mini 跑 Flutter iOS CI

你的情況繼續蹭同事 Mac/買二手本上雲端 M4 Mac mini
每月 iOS 發版 ≥ 4 次協調成本高建議 固定建置機 + 快取
團隊無 Mac,僅 Flutter難持續日租試跑 一輪真實 build ipa
已有 Mac mini 放辦公室 7×24自建即可雲 Mac 作災備或峰值擴容
大量 iOS 插件需調原生本地 Mac 仍方便雲機編 release,本地編 debug

10)FAQ(Flutter iOS CI/CD)

Flutter iOS CI 和原生 iOS CI 有何不同?
多一層 flutter pub get 與 plugin 程式碼產生,且版本必須與工作區 .fvmrc 一致;Xcode 側仍是 Pods + archive。

和 Codemagic/託管 macOS 比?
託管適合快速驗證;當二次建置要穩定在 10 分鐘內、且要強快取時,Flutter iOS build on Mac mini + self-hosted Runner 更可控。

第一次跑通 Flutter iOS CI 要多久?
熟手約 1 個工作日:裝 Xcode/Flutter、註冊 Runner、配簽章、跑通一條 build ipa 並上傳 TestFlight。

工程結論:Flutter iOS CI 最小可行方案(MVF)

若你的 Flutter 專案每週至少發布一次 iOS 包,且團隊存在 Windows/Linux 主力機,那麼可落地的最小方案是:

  1. 1 台 16GB Mac mini M4(實體機或獨享雲主機均可)專職 Flutter iOS CI
  2. 註冊 self-hosted macOS Runner,與 Linux 側 flutter testbuild apk 分 workflow;
  3. 持久化 .pub-cache、CocoaPods cache、DerivedData 三層快取;
  4. 目標:48 小時內用真實倉庫跑通第一條 flutter build ipa → TestFlight,再決定是否加第二台做併發或災備。

我們用同一套 MVF 把二次建置壓進 4–8 分鐘 檔(見第 5 節對比表)。需要獨享 Mac mini 節點與 SSH 交付時,可參考 定價方案幫助中心 對照自己的發版頻率算帳——先驗證 pipeline,再談規模化。