這篇只談一件事: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 install → xcodebuild 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 test、build apk 留在 Linux;Flutter iOS CI 專機只跑 iOS 整合建置與簽章相關步驟。2)Flutter iOS CI 雲端架構圖
下面是我們線上在用的 Flutter iOS CI/CD 拓撲:開發者不必本地有 Mac,iOS 包由 self-hosted Runner 產出。
Flutter iOS CI 雲端架構
本地:Dart · Android · git push
ubuntu-latestflutter test · build apk
labels: macos, flutter-ios
fvm flutter build ipapod install · xcodebuild archive
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 Runner | PR 觸發 Linux job | 標籤 macos 的 self-hosted Runner | 發版頻繁、要強快取 |
我們日常是 A + C:合併後 Linux 跑 flutter test;Flutter iOS CI 僅在 tag/main 合併時由 Mac mini Runner 觸發。模式 B 適合短期無 Mac 接入,長期仍建議 Runner 化。
4)Flutter iOS CI 環境清單(Mac mini 一次性裝好)
- Xcode + CLT: 與
ios/Podfile、Flutter 通道要求的最低版本對齊;裝完跑sudo xcodebuild -license accept。 - Flutter SDK: 用
fvm或固定目錄~/flutter,在倉庫寫.fvmrc,避免同事與 CI 各用一版。 - CocoaPods:
gem install cocoapods或 Homebrew;大倉建議鎖Podfile.lock並提交。 - 簽章: 團隊用 Fastlane Match 或 Xcode 自動簽章;p12 與描述檔不要進 Git,放加密倉庫或 CI Secret。
- 網路: 節點盡量靠近 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 上」。
| 環境 | 首次建置(冷啟動) | 二次建置(快取命中) |
|---|---|---|
| 本地 MacBook Air M2(8GB,合蓋/降頻) | 18–25 min | 12–15 min |
GitHub 託管 macos-latest(無自訂快取) | 16–22 min | 14–18 min |
| Mac mini M4(16GB)· Flutter iOS CI + 三層快取 | 15–20 min | 4–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。
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.yml,runs-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-latest上flutter test、build 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 台 16GB Mac mini M4(實體機或獨享雲主機均可)專職 Flutter iOS CI;
- 註冊 self-hosted macOS Runner,與 Linux 側
flutter test/build apk分 workflow; - 持久化
.pub-cache、CocoaPods cache、DerivedData 三層快取; - 目標:48 小時內用真實倉庫跑通第一條
flutter build ipa→ TestFlight,再決定是否加第二台做併發或災備。
我們用同一套 MVF 把二次建置壓進 4–8 分鐘 檔(見第 5 節對比表)。需要獨享 Mac mini 節點與 SSH 交付時,可參考 定價方案 與 幫助中心 對照自己的發版頻率算帳——先驗證 pipeline,再談規模化。