← Back to tech blog

Flutter iOS CI/CD: build IPA on Mac mini M4 with CocoaPods cache tuning

Flutter iOS CI/CD pipeline: flutter build ipa and self-hosted runner on Mac mini M4
Typical Flutter iOS CI/CD split: Dart on Windows/Linux, IPA from a Mac mini M4 runner.

This article is about one thing: Flutter iOS CI/CD (primary anchor: Flutter iOS build on Mac mini) — how to run flutter build ipa reliably on a Mac mini M4, split CocoaPods caching, a self-hosted macOS Runner, and Linux-side Android/test jobs. Flutter’s real bottleneck is rarely Dart; it’s centralized dependence on the iOS build chain: pod installxcodebuild archive → signing → TestFlight upload — all Mac-only. Our team’s default split: Android on Windows/Linux CI, Flutter iOS CI on one dedicated rack Mac mini. Mixing native Xcode? See offloading Xcode builds; runner setup and TCO: self-hosted macOS runners.

1) Why Flutter iOS CI needs its own Mac

“One codebase” reuses business logic, not build planes. iOS artifacts still ride the full Xcode pipeline — you cannot run Flutter iOS CI on ubuntu-latest. Hosted macOS minutes are expensive, queues drift, and without persistent cache every flutter build ipa snaps back to the 15–25 minute band. Flutter iOS CI/CD on an always-on Mac mini buys: pinned Flutter/Xcode, three reusable cache layers, 24/7 without lid-close sleep — so Android and iOS release cadences actually decouple.

Scope: keep flutter test and build apk on Linux. The Flutter iOS CI host only does iOS integration builds and signing.

2) Flutter iOS CI cloud architecture

Production Flutter iOS CI/CD topology we run today — developers don’t all need a local Mac; the IPA comes from a self-hosted runner.

Flutter iOS CI in the cloud

Developer (Windows / Linux / Mac)
local: 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

Persistent cache on the Mac mini (what makes Flutter iOS CI fast)

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

3) Three ways to run Flutter iOS CI

ModeLocal / other CICloud M4 MacBest for
A. iOS build only in clouddaily flutter run, Android packagesbuild ipa, store uploadWin/Linux + device
B. Fully remote Flutter (rare)editor + git onlysimulator, green flutter doctorno local Mac, OK with VNC lag
C. Cloud Mac = iOS runnerPR triggers Linux jobself-hosted runner, label macosfrequent releases, strong cache

We run A + C: merge → flutter test on Linux; Flutter iOS CI on tag/main via the Mac mini runner. B works for short stints without a Mac; long term, runner-ify it.

4) Flutter iOS CI environment checklist (one-time on Mac mini)

  1. Xcode + CLT: Match ios/Podfile and Flutter channel minimums; then sudo xcodebuild -license accept.
  2. Flutter SDK: fvm or fixed ~/flutter; commit .fvmrc so devs and CI share a version.
  3. CocoaPods: gem install cocoapods or Homebrew; large repos: commit Podfile.lock.
  4. Signing: Fastlane Match or Xcode automatic signing; never commit p12/profiles — encrypted store or CI secrets.
  5. Network: Region near Git remote and pub.dev/CDN; corporate Git may need VPN — see help center egress notes.

On first boot run flutter doctor -v and paste into the team wiki — that “gold snapshot” is your first triage reference.

5) CocoaPods cache: the Flutter iOS CI speed lever (Before / After)

Same medium Flutter repo (~40+ plugins, release IPA), three runs per environment, median times — Flutter iOS CI variance is almost entirely “does cache stay on the Mac mini?”.

Full build ipa duration for Flutter iOS CI (minutes, same repo · measured May 2026)
EnvironmentFirst build (cold)Second build (cache hit)
Local MacBook Air M2 (8GB, lid closed / throttle)18–25 min12–15 min
GitHub macos-latest (no custom cache)16–22 min14–18 min
Mac mini M4 (16GB) · Flutter iOS CI + 3 cache layers15–20 min4–8 min

Second builds drop from 12+ minutes to single digits because of the three persistent layers in the diagram — that’s when Flutter iOS build on Mac mini graduates from “it works” to “it’s CI”.

  • PUB_CACHE / ~/.pub-cache: fixed path in runner env, reuse Dart deps across jobs.
  • CocoaPods cache + ios/Pods: unchanged Podfile.lock → skip pod install; else incremental.
  • DerivedData: pin ~/DerivedData; no clean unless Xcode major bump.
Numbers: at ≥1 iOS release/week, cached Flutter iOS CI on Mac mini saves ~8–12 minutes per pipeline — four releases/month × three devs waiting is a full workday of queue time.

6) Flutter iOS CI command flow (SSH or runner script)

From repo root; swap Flutter version for your lockfile:

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

In GitHub Actions, put steps in release-ios.yml with runs-on: [self-hosted, macos, flutter-ios]. TestFlight uploads often ride a more stable path from the Mac mini than home Wi‑Fi.

7) GitHub Actions: split Flutter iOS CI/CD workflows

Two workflows (or matrix + if):

  • test-android.yml: ubuntu-latestflutter test, build apk.
  • release-ios.yml: runs-on: [self-hosted, macos, flutter] — tag or main merge only: build ipa + upload.

If VNC debug and runners share one cloud Mac, use a separate build user so manual edits don’t fight checkout. Concurrency/disk: runner TCO. Windows-first teams: Xcode on Windows + cloud Mac — same idea for Flutter without local Xcode.

8) When Flutter iOS CI stalls (how / fix)

Matches typical why / how / fix / error searches — our wiki entry points.

pod install takes 10+ minutes — is that normal?

Why: runner checks out to a temp dir every job, CocoaPods download cache not mounted, or pod install --repo-update in the default script.
Fix: fixed WORK path; hash Podfile.lock and skip when unchanged; move --repo-update to a weekly maintenance job.

xcodebuild archive fails / exit code 65

Why: Flutter/Xcode mismatch, bad DerivedData, native plugin incompatible with SDK.
Fix: log fvm flutter doctor -v at job start; wipe DerivedData and retry; check Xcode release notes for your channel.

signing failed / provisioning mismatch

Why: p12 not in keychain, bundle id ≠ profile, expired Match repo.
Fix: dedicated keychain on Mac mini runner; renew Fastlane Match; VNC “always trust” if needed — the top non-code failure in Flutter iOS CI.

DerivedData fills the disk

Why: parallel branch jobs, old archives, multiple Flutter versions.
Fix: cron ~/Library/Developer/Xcode/Archives; runner concurrency 1–2; disk ≥512GB or expand on schedule.

Flutter version drift → plugin build errors

Why: devs skip fvm, CI uses another channel.
Fix: commit .fvmrc; CI runs fvm install && fvm flutter pub get; PR template checkbox for SDK bumps.

9) When a dedicated Mac mini for Flutter iOS CI pays off

Your situationBorrow a Mac / buy usedCloud M4 Mac mini
≥4 iOS releases/monthcoordination taxrecommended fixed builder + cache
no Mac, Flutter onlyhard to sustainday rental on real build ipa
office Mac mini 24/7self-host is enoughcloud Mac for DR/peak
heavy native pluginslocal Mac still handyrelease in cloud, debug local

10) FAQ (Flutter iOS CI/CD)

Flutter iOS CI vs native iOS CI?
Extra flutter pub get and plugin codegen; version must match .fvmrc; Xcode side is still Pods + archive.

vs Codemagic / hosted macOS?
Hosted is fine for a quick proof; when second builds must sit under 10 minutes with strong cache, Flutter iOS build on Mac mini + self-hosted runner wins on control.

Time to first green pipeline?
~1 working day for experienced teams: Xcode/Flutter, runner, signing, one build ipa → TestFlight.

Engineering conclusion: minimum viable Flutter iOS CI (MVF)

If you ship iOS from Flutter at least weekly and daily drivers are Windows/Linux, the smallest setup that holds:

  1. One 16GB Mac mini M4 (on-prem or dedicated cloud) for Flutter iOS CI only;
  2. self-hosted macOS runner, separate workflows for Linux flutter test / build apk;
  3. persistent .pub-cache, CocoaPods cache, DerivedData;
  4. goal: first flutter build ipa → TestFlight on a real repo within 48 hours, then decide on a second machine for concurrency/DR.

Same MVF puts repeat builds in the 4–8 minute band (section 5 table). For dedicated Mac mini nodes over SSH, weigh plans and help center against release frequency — validate the pipeline before you scale.