CI/CD for Flutter with Codemagic
We migrated from a GitHub Actions macOS runner that cost $200/month and broke every time Apple rotated a certificate. Codemagic's Flutter-native workflows cut our pipeline config in half and TestFlight uploads became boring—which is exactly what CI/CD should be. If you're shipping Flutter to both stores, Codemagic (or a similarly Flutter-aware CI) beats duct-taping generic runners.
Project structure for CI
Commit codemagic.yaml at repo root:
workflows:
ios-android-release:
name: Release Build
max_build_duration: 60
instance_type: mac_mini_m2
environment:
flutter: stable
xcode: latest
groups:
- app_store_credentials
- keystore_credentials
vars:
PACKAGE_NAME: com.example.myapp
ios_signing:
distribution_type: app_store
bundle_identifier: com.example.myapp
triggering:
events:
- push
branch_patterns:
- pattern: main
include: true
scripts:
- name: Get packages
script: flutter pub get
- name: Analyze
script: flutter analyze
- name: Test
script: flutter test
- name: Build Android
script: |
flutter build appbundle --release \
--build-number=$PROJECT_BUILD_NUMBER
- name: Build iOS
script: |
flutter build ipa --release \
--build-number=$PROJECT_BUILD_NUMBER \
--export-options-plist=/Users/builder/export_options.plist
artifacts:
- build/**/outputs/**/*.aab
- build/ios/ipa/*.ipa
publishing:
app_store_connect:
auth: integration
submit_to_testflight: true
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: internal
Credential management
iOS — App Store Connect API key (preferred over Apple ID):
- App Store Connect → Users → Integrations → App Store Connect API.
- Upload
.p8key to Codemagic encrypted environment group. - Reference via
app_store_connect: auth: integration.
Android — keystore:
Upload keystore file and set environment variables:
environment:
groups:
- keystore_credentials # CM_KEYSTORE, CM_KEYSTORE_PASSWORD, etc.
Reference in android/app/build.gradle via environment or key.properties generated in CI:
echo "storePassword=$CM_KEYSTORE_PASSWORD" >> android/key.properties
echo "keyPassword=$CM_KEY_ALIAS_PASSWORD" >> android/key.properties
echo "keyAlias=$CM_KEY_ALIAS" >> android/key.properties
echo "storeFile=$CM_KEYSTORE_PATH" >> android/key.properties
Never commit keystores or key.properties to git.
Caching for speed
Codemagic caches by default, but explicit cache paths help:
cache:
cache_paths:
- $FLUTTER_ROOT/.pub-cache
- $HOME/.gradle/caches
- $HOME/Library/Caches/CocoaPods
First build populates cache; subsequent builds often drop from 18 minutes to 8 on M2 instances.
Branch workflows
Separate PR validation from release:
workflows:
pr-check:
name: PR Validation
instance_type: mac_mini_m2
triggering:
events:
- pull_request
scripts:
- flutter pub get
- flutter analyze --fatal-infos
- flutter test --coverage
publishing:
scripts:
- name: Upload coverage
script: |
# codecov or similar
bash <(curl -s https://codecov.io/bash)
Release workflow triggers only on tagged commits:
triggering:
events:
- tag
tag_patterns:
- pattern: 'v*'
include: true
Versioning and build numbers
Codemagic exposes PROJECT_BUILD_NUMBER (auto-incrementing per workflow) and BUILD_NUMBER. Sync with pubspec:
# Increment build number from Codemagic
flutter pub run build_runner build --delete-conflicting-outputs || true
sed -i '' "s/version: \(.*\)+.*/version: \1+$PROJECT_BUILD_NUMBER/" pubspec.yaml
flutter build ipa --build-number=$PROJECT_BUILD_NUMBER
Or use codemagic-cli-tools for semantic versioning from git tags.
Integration tests on Codemagic
Run Patrol or integration tests on emulators:
scripts:
- name: Integration tests
script: |
flutter emulators --launch apple_ios_simulator
flutter test integration_test/
Emulator tests on macOS builders are slower—reserve for nightly builds; unit tests on every PR.
Fastlane alternative within Codemagic
Codemagic publishing replaces much of Fastlane for standard flows. Use Fastlane when you need custom lanes—screenshots, metadata sync, phased rollout:
scripts:
- name: Fastlane deploy
script: |
cd ios && fastlane beta
Install gems in a prior step; commit Fastfile to repo.
Monitoring and notifications
Configure Slack or email in Codemagic UI for build failures. Tag @channel only on main branch failures—PR failures notify the author via GitHub status checks.
Track build duration trends. Spikes usually mean cache invalidation or new heavy dependencies.
Shorebird and OTA alongside CI
If using Shorebird for code push, run shorebird release in Codemagic after store builds for teams shipping Dart hotfixes without store review. Separate workflows—store release vs patch—prevent accidentally patching debug builds. Store release artifacts with build IDs matching Shorebird patch metadata for rollback traceability.
Codemagic macOS M2 instances worth the cost for iOS—Intel runners timeout on large Flutter iOS builds. Monitor queue times; upgrade instance tier during release weeks.
Store Codemagic signing files encrypted; rotate certificates before expiry with calendar reminders 30 days ahead. Parallel Android and iOS build steps in one workflow when teams release both stores simultaneously—artifact naming with flavor and build number prevents wrong IPA upload confusion.
Production teams should treat this guidance as living documentation: revisit assumptions after major platform upgrades, measure outcomes with real metrics rather than checklist compliance, and pair written standards with automated checks in CI. The patterns here reflect what held up in shipped apps—not theoretical perfection. Adapt thresholds, timeouts, and tooling to your stack, but keep the underlying principles: explicit configuration, testable behavior, and failure modes users can understand.
When onboarding new engineers, walk through one end-to-end example in a debug build before asking them to extend the pattern. Most failures I have seen came from skipped platform setup steps—manifest entries, API keys, code generation, or permission prompts—not from misunderstanding the Dart layer. Keep a troubleshooting section in your team wiki linking official docs and the exact commands that worked for your last upgrade.
Schedule a quarterly review of this implementation against current SDK release notes—Flutter and cloud providers ship breaking changes on six-month cadences, and assumptions that held last year may need adjustment. Capture before-and-after metrics when you change configuration so regressions are obvious in retrospect rather than debated from memory.
Pin Flutter SDK version in CI to match local .fvmrc — channel drift causes builds that pass locally and fail in pipeline.
Resources
- Codemagic Flutter Documentation
- codemagic.yaml reference
- Codemagic iOS Code Signing
- Codemagic Android Code Signing
- Flutter Continuous Delivery
Frequently asked questions
What is Codemagic for Flutter?
Codemagic is a CI/CD platform with first-class Flutter support—macOS and Linux builders, built-in code signing for iOS and Android, and YAML-based workflows. It handles flutter build ipa and appbundle with less configuration than generic CI runners, especially for Apple certificate management via Codemagic's encrypted storage.
How do I configure iOS signing in Codemagic?
Upload your App Store Connect API key, distribution certificate, and provisioning profile in Codemagic Team settings or reference them via environment groups in codemagic.yaml. Codemagic installs certificates into a temporary keychain during the build. Automatic code signing can fetch profiles if you provide API key credentials.
Can Codemagic run Flutter tests before building?
Yes—add scripts in the workflow to run flutter analyze, flutter test, and integration tests before the build step. Fail fast on test failures to avoid burning macOS build minutes. Cache pub dependencies and Gradle artifacts to keep test-plus-build pipelines under 15 minutes.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →