Speed Up Flutter in GitHub Actions
Last updated
To speed up Flutter in GitHub Actions, cache the Flutter SDK and pub packages with subosito/flutter-action, run analysis and tests on Linux, build Android on Linux, and keep macOS only for the iOS build. macOS minutes cost several times more than Linux minutes, and most of a Flutter pipeline does not need a Mac.
This guide is the Flutter version of our general guide to speeding up GitHub Actions.
Where the time goes
A Flutter pipeline has four parts: install the SDK, flutter pub get, flutter analyze plus flutter test, and platform builds (flutter build apk or appbundle, flutter build ios). On a fresh VM the SDK download is a few hundred megabytes, and the native builds (Gradle for Android, Xcode and CocoaPods for iOS) are the slowest. The test and analyze steps are quick by comparison.
SDK and pub cache with flutter-action
subosito/flutter-action installs Flutter and can cache it. Its README documents these inputs:
channel:stable,betaormaster.flutter-version: a version like3.19.0, or a partial like3.x.flutter-version-file: read the version frompubspec.yamlor.fvmrc. The README warns that the exact version must be in the file, so ranges will not work.cache: caches the Flutter SDK. Ifpub-cacheis not set, it also controls pub dependency caching.cache-key,pub-cache-keyandcache-path: custom keys and paths. They accept placeholders such as:os:,:channel:,:version:,:arch:and:hash:.
- uses: actions/checkout@v7
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true
- run: flutter pub get
- run: flutter analyze
- run: flutter test --coverage
Pin the version (through the file or flutter-version) rather than following stable blindly. A new stable release changes the SDK cache key, and your build changes under you. A pinned version also makes your cache hit rate predictable.
Test speed
flutter test runs test files in parallel. It uses the --concurrency option, which defaults to the number of cores minus something small, so more cores help automatically. Practical tips:
- Split fast widget and unit tests (
flutter test) from slower integration tests (integration_test/), which need a device or emulator and are slow to start. - Use
flutter test --coverageonly if you upload coverage; it adds time. - If your repo has several packages (a monorepo with Melos), run
flutter testper package in a matrix, or use Melos to run scripts in parallel. - For golden tests, generate and compare on one OS. Fonts differ across platforms, and Linux is the cheapest place to run them.
Android builds on Linux
Android builds run Gradle, and Gradle needs its own caching. Add actions/setup-java and gradle/actions/setup-gradle; see our Gradle guide for the settings:
build-android:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: 17
- uses: gradle/actions/setup-gradle@v6
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true
- run: flutter pub get
- run: flutter build apk --release
- uses: actions/upload-artifact@v7
with:
name: apk
path: build/app/outputs/flutter-apk/app-release.apk
Use flutter build apk --split-per-abi only if you need separate APKs; it builds once per ABI and takes longer. For store uploads, build an app bundle (appbundle) once. Check which Java version your Android Gradle Plugin version requires before pinning one.
iOS builds on macOS, as little as possible
flutter build ios needs Xcode, so it needs macOS. Keep that job separate and run it only when needed, for example on main or release tags, not on every PR push. Cache CocoaPods by caching ios/Pods keyed on ios/Podfile.lock:
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version-file: pubspec.yaml
cache: true
- uses: actions/cache@v6
with:
path: ios/Pods
key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }}
- run: flutter pub get
- run: flutter build ios --release --no-codesign
--no-codesign is right for a compile check; use real signing only in the release job. For macOS cost and options, see macOS runners for iOS CI and speed up Xcode builds in CI. Hosted macOS is billed at a higher per-minute rate than Linux, so every minute you move to Linux saves real money.
A job layout that works
analyze-teston Linux for every push: pub get, analyze, tests.build-androidon Linux for every push to PRs touchingandroid/orlib/.build-ioson macOS formainand tags only.
Use path filters and concurrency to cancel superseded runs.
What more CPU cores buy you
Flutter tests run in parallel and Gradle uses several cores, so a bigger Linux machine helps analyze, test and Android builds. The Xcode step does not benefit from Linux cores at all. We have no Flutter row in our benchmarks, so we do not quote a speedup. compiler.dev's default Linux machine is 8 vCPU / 16 GB at $0.012 per minute. macOS is in early access, with an Apple M4 Pro 7-core VM at $0.028 per minute and a 14-core VM at $0.055 per minute. See pricing and runner pricing. Switching is one line, runs-on: [self-hosted, Linux, compiler-ci].
Related: the Flutter template, React Native guide and GitHub Actions caching.
FAQ
Does flutter-action cache the pub cache?
Yes. With cache: true it caches the SDK, and pub dependencies too unless you set pub-cache separately. You can set custom keys with pub-cache-key.
Can I build an iOS app on Linux?
No. iOS builds need Xcode on macOS. Build Android and run tests on Linux.
Should I pin the Flutter version?
Yes. Use flutter-version or flutter-version-file so the SDK and cache key only change when you decide.
How do I run Flutter integration tests faster?
Run them in a separate job, use a headless target such as a Linux desktop device when your app supports it, and run only on main if they are slow.
Related
- Add a CI Speed Badge to Your READMEShow how fast your CI is with a README badge: the median GitHub Actions run time for your repository, updated automatically. Free for public repos.
- Speed Up .NET in GitHub ActionsSpeed up .NET builds in GitHub Actions: NuGet caching with setup-dotnet, --no-restore, --no-build, parallel dotnet test, and SQL Server services.
- Speed Up Bazel in GitHub ActionsSpeed up Bazel in GitHub Actions with setup-bazel, a disk cache, repository cache, a remote cache, and the right --jobs setting. Working YAML.
- Speed Up Django in GitHub ActionsSpeed up Django tests in GitHub Actions with pip or uv caching, pytest-xdist, --reuse-db, a Postgres service, and test splitting. Working YAML.