CI/CD for Flutter with GitHub Actions
One Flutter codebase, two app stores -- so split the cheap checks from the platform builds.
Flutter lets you ship to iOS and Android from one codebase, but CI still has to respect each platform toolchain. Analysis and tests are pure Dart and run on cheap Linux runners; the Android build also runs on Linux, while the iOS build needs macOS. This recipe covers the Linux path and shows where the macOS split belongs.
What the pipeline does
- Installs the Flutter SDK with subosito/flutter-action.
- Runs static analysis with flutter analyze.
- Runs the widget and unit test suite.
- Builds an Android APK artifact.
The workflow
Pin a channel and version so the Flutter SDK on the runner is reproducible across runs.
name: Flutter CI
on: [push, pull_request]
jobs:
analyze-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: '3.22.0'
- run: flutter pub get
- run: flutter analyze
- run: flutter test
- run: flutter build apk --release
- uses: actions/upload-artifact@v4
with:
name: app-release-apk
path: build/app/outputs/flutter-apk/app-release.apkNotes for this platform
Keep flutter analyze and flutter test on Linux -- they are fast and have no platform dependency. The Android build also stays on Linux. Only flutter build ios (or ipa) needs a macos runner, so add a separate macOS job gated to release branches rather than running macOS on every push. Because the test and analyze stages dominate your daily runs and run on Linux, choosing faster, cheaper Linux runners for them is where most of the savings come from. Managed runners also auto-retry transient pub-get and network failures so a flaky package fetch does not fail the build.
Key takeaways
- Dart analysis, tests, and the Android build all run on cheap Linux runners.
- Only the iOS build needs macOS -- gate that job to release branches.
- Pin the Flutter channel and version for reproducible SDK installs.