Codemagic CI/CD: Build and Deploy Flutter Applications
Codemagic has quickly become the go‑to CI/CD platform for Flutter developers who want to ship high‑quality apps without wrestling with complex server setups. In this guide we’ll walk through everything you need to get a Flutter project from source control to the Google Play Store or Apple App Store using Codemagic’s visual pipelines, YAML configuration, and built‑in integrations. By the end, you’ll be able to automate builds, run tests, manage signing keys, and deploy with a single push.
Why Choose Codemagic for Flutter?
Flutter’s hot‑reload and single‑code‑base promise speed, but the real delivery speed comes from a reliable CI/CD pipeline. Codemagic is built by the Flutter community, so it understands the nuances of Dart, platform channels, and the iOS/Android toolchains out of the box. It also offers free minutes for open‑source projects, which makes it a low‑risk way to experiment before committing to a paid plan.
Another advantage is the seamless integration with popular services like GitHub, GitLab, Bitbucket, and even Firebase App Distribution. You can trigger builds on every pull request, tag, or manual dispatch, and the platform automatically caches Flutter dependencies to shave minutes off each run. This means faster feedback loops and fewer “it works on my machine” surprises.
Getting Started: Connecting Your Repository
First, sign up at codemagic.io and authorize access to your Git provider. Click “Add application”, select the repository, and Codemagic will scan the project to detect the Flutter configuration. If your repo uses a monorepo layout, you can specify the sub‑directory containing pubspec.yaml under “Project settings”.
After the initial connection, Codemagic creates a default pipeline that builds an APK for Android and an IPA for iOS. You can view the generated codemagic.yaml by navigating to “Workflow editor → YAML”. This file is the single source of truth for your CI/CD logic, and you can edit it directly in the UI or commit it to your repo for version control.
Understanding the codemagic.yaml Structure
The YAML file consists of three main sections: workflows, environment, and scripts. Each workflow defines a series of steps that run on a specific machine type (macOS for iOS, Linux for Android). Below is a minimal example that builds both platforms and uploads artifacts to Codemagic’s storage.
workflows:
flutter-build:
name: Flutter Build & Deploy
environment:
flutter: stable
xcode: latest
cocoapods: default
scripts:
- name: Install dependencies
script: flutter pub get
- name: Run unit tests
script: flutter test
- name: Build Android APK
script: flutter build apk --release
- name: Build iOS IPA
script: flutter build ios --release --no-codesign
artifacts:
- build/app/outputs/flutter-apk/app-release.apk
- build/ios/ipa/*.ipa
Notice the --no-codesign flag for iOS – this tells Codemagic to skip signing during the build, which is useful when you want to handle signing in a later step (e.g., using Fastlane). The artifacts block tells Codemagic which files to preserve after the build finishes.
Setting Up Environment Variables and Secrets
Storing API keys, keystore passwords, and Apple credentials in plain text is a security nightmare. Codemagic solves this with “Environment variables” and “Secure files”. Navigate to “Settings → Environment variables”, add entries like ANDROID_KEYSTORE_PASSWORD, and mark them as “Secure”. These values are injected into the build container at runtime.
For iOS signing, upload your .p12 certificate and provisioning profile under “Secure files”. Then reference them in the YAML using the $CM_SIGNING_CERTIFICATE and $CM_PROVISIONING_PROFILE placeholders. Codemagic will automatically import the certificate into the keychain and associate the profile with the build.
Pro tip: Keep separate environment variable sets for “development”, “staging”, and “production”. Codemagic lets you clone a workflow and switch the variable group, so you can test a new feature on a staging build without affecting the production pipeline.
Running Automated Tests in the Pipeline
Testing is the cornerstone of any CI/CD system. Codemagic supports unit tests, widget tests, and integration tests out of the box. The flutter test command runs both unit and widget tests, while flutter drive executes integration tests on a real device or emulator.
Below is a snippet that spins up an Android emulator, runs integration tests, and then shuts down the emulator. This ensures your UI flows work on a real device before you ship the binary.
- name: Start Android emulator
script: |
echo "y" | $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-33;google_apis;x86_64"
$ANDROID_HOME/tools/bin/avdmanager create avd -n test -k "system-images;android-33;google_apis;x86_64" --device "pixel"
$ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window &
adb wait-for-device
adb shell input keyevent 82
- name: Run integration tests
script: flutter drive --target=test_driver/app.dart
- name: Stop emulator
script: adb -s emulator-5554 emu kill
Because Codemagic caches the Android SDK, the emulator startup only adds a few seconds to each run after the first build. For iOS, you can use the built‑in “Xcode simulator” runner in a similar fashion.
Managing Code Signing for Android
Android signing requires a keystore file (.jks) and its password. Upload the keystore as a “Secure file”, then reference it in the build command. Here’s a practical example that signs the APK automatically:
- name: Decode keystore
script: |
echo $ANDROID_KEYSTORE_BASE64 | base64 --decode > $HOME/key.jks
- name: Build signed APK
script: |
flutter build apk \
--release \
--build-number=$CM_BUILD_NUMBER \
--build-name=$CM_BUILD_NAME \
--dart-define=FLAVOR=production \
--obfuscate \
--split-per-abi \
--target-platform=android-arm,android-arm64,android-x64 \
--keystore=$HOME/key.jks \
--keystore-password=$ANDROID_KEYSTORE_PASSWORD \
--key-alias=$ANDROID_KEY_ALIAS \
--key-password=$ANDROID_KEY_PASSWORD
The --split-per-abi flag generates separate APKs for each CPU architecture, reducing download size for end users. The --obfuscate option makes reverse engineering harder, which is especially important for commercial apps.
Pro tip: Store the keystore in a separate GitHub repository with restricted access. Then use Codemagic’s “Secure file” feature to fetch it at build time, keeping the key out of your main codebase.
iOS Signing with Fastlane Integration
While Codemagic can handle basic iOS signing, many teams prefer Fastlane for its powerful match, gym, and pilot tools. Fastlane’s match syncs certificates and provisioning profiles via a private Git repo, while pilot uploads the IPA to TestFlight.
Below is a concise Fastlane lane that you can call from Codemagic to sign and distribute an iOS build:
lane :ci_deploy do
match(type: "appstore", readonly: true)
build_app(
scheme: "MyApp",
export_method: "app-store",
output_directory: "./build/ios",
export_options: {
uploadBitcode: false,
compileBitcode: false
}
)
upload_to_testflight(skip_waiting_for_build_processing: true)
end
To invoke this lane from Codemagic, add a script step that runs bundle exec fastlane ci_deploy. Make sure you have a Gemfile with Fastlane listed, and let Codemagic install the gems automatically.
Deploying to Google Play Store
Publishing to Google Play can be fully automated using the Google Play Publisher API. Codemagic provides a built‑in “Google Play” integration where you upload a JSON service account key as a secure file. Then, add a deployment step that calls fastlane supply or the native gcloud CLI.
Here’s a quick Fastlane lane that pushes the signed APK to the internal testing track:
lane :publish_to_play do
upload_to_play_store(
track: "internal",
aab: "./build/app/outputs/bundle/release/app-release.aab",
json_key: ENV["PLAY_STORE_JSON_KEY"]
)
end
When the lane finishes, Codemagic will display a link to the newly uploaded build in the Play Console, allowing you to promote it to beta or production with a few clicks.
Pro tip: Use the “rollout percentage” feature in the Play Console to gradually release new versions to a subset of users, then monitor crash reports before a full rollout.
Real‑World Use Case: E‑Commerce App with Staged Deployments
Imagine you run an e‑commerce Flutter app that serves millions of users worldwide. You need to ensure that new features (e.g., a personalized recommendation engine) don’t break checkout flows. By configuring two separate Codemagic workflows—one for “staging” and one for “production”—you can automatically deploy every merge to a staging track on TestFlight and an internal testing track on Google Play.
In the staging workflow, enable Firebase App Distribution to push the build to a group of QA testers. Add a script that runs integration tests against a staging backend, then send a Slack notification with the build link. Once the QA sign‑off is received, a manual “Promote to Production” button in Codemagic triggers the production workflow, which uses the same code but swaps environment variables for the live API endpoints.
This pattern gives you continuous delivery without sacrificing control. You can also add a “Canary” workflow that releases a small percentage of users to the new version using Firebase Remote Config, allowing you to gather real‑world metrics before a full rollout.
Advanced Optimizations: Caching and Parallelism
Codemagic automatically caches the Flutter SDK, Pub packages, and CocoaPods, but you can fine‑tune caching for large assets like images or generated protobuf files. Add a cache section to your YAML to persist custom directories between builds.
cache:
cache_paths:
- $HOME/.pub-cache
- $HOME/.gradle/caches
- assets/generated
Parallelism can further reduce pipeline time. Codemagic allows you to split the workflow into multiple “jobs” that run concurrently on separate machines. For example, you could run Android and iOS builds in parallel, then merge the results in a final “artifact aggregation” step.
Monitoring, Debugging, and Rollbacks
Every build generates a detailed log that you can view in real time or download for offline analysis. Use the “Search” feature to quickly locate failed steps, and click the “Retry” button to re‑run a job without triggering a full pipeline. If a deployment fails, Codemagic retains the previous successful artifacts, making rollbacks as simple as re‑triggering the last good build.
Integrations with Sentry, Bugsnag, and Firebase Crashlytics allow you to automatically upload source maps and symbol files, ensuring that crash reports are readable. Add a script step at the end of the pipeline to push the .dSYM and mapping.txt files to your crash reporting service.
Pro tip: Enable “Auto‑cancel redundant builds” in the project settings. This prevents a backlog of builds when multiple commits land in quick succession, saving both compute minutes and developer time.
Cost Management and Scaling
While the free tier offers generous minutes for small projects, larger teams may need to purchase additional build minutes or dedicated machines. Codemagic’s pricing model is transparent—pay per minute for Linux builds and per build for macOS. If you run many iOS builds, consider reserving a macOS machine to avoid queue delays.
To keep costs under control, set up “build quotas” per branch, and use “Scheduled builds” only for nightly regression runs. This way, you avoid unnecessary builds on feature branches that are still in active development.
Best Practices Checklist
- Versioning: Use
flutter --version-nameand--build-numberto keep app versions in sync with Git tags. - Secure Secrets: Store all keys, passwords, and certificates as secure variables or files.
- Testing Pyramid: Run unit tests on every commit, widget tests on PRs, and full integration tests on nightly builds.
- Artifact Retention: Keep only the last 10 builds to save storage while retaining rollback capability.
- Documentation: Keep the
codemagic.yamlfile versioned and include comments explaining each step.
Conclusion
Codemagic transforms the often‑painful process of building, testing, and deploying Flutter applications into a smooth, repeatable workflow. By leveraging its YAML configuration, secure environment handling, and native integrations with Fastlane, Google Play, and TestFlight, you can achieve continuous delivery with confidence. Whether you’re a solo developer launching a hobby project or a large team maintaining a production‑grade e‑commerce app, the patterns covered here—caching, parallel builds, staged deployments, and proactive monitoring—provide a solid foundation for scaling your Flutter CI/CD pipeline.