Skip to content
← All writing
July 8, 2026·4 min read

CI/CD for Flutter: Automating Builds and Store Releases

Shipping a Flutter release by hand is a ritual: bump the version, run the tests you remember to run, build twice, fiddle with signing, upload to two consoles, fill in release notes. It takes an hour when nothing goes wrong, and something eventually goes wrong. Automating it isn't a luxury for big teams — it's how solo developers ship weekly without dreading it.

Automate in stages, not all at once

The mistake is trying to build the full pipeline in a weekend. Each stage pays for itself independently, so add them in order:

  1. CI on every pull request — analyze, test, build.
  2. Automated release builds — signed artifacts produced by the pipeline, not your laptop.
  3. Automated store delivery — artifacts uploaded to TestFlight and Play internal testing.
  4. Promotion to production — still a human decision, just a one-click one.

Stage 1 alone eliminates "it worked on my machine." Most apps are well served stopping at stage 3 and promoting manually.

Stage 1: the quality gate

Every push should run the checks you'd otherwise run from memory:

- run: flutter pub get
- run: flutter analyze
- run: dart format --set-exit-if-changed .
- run: flutter test

Any general CI service can do this with a Flutter setup step; specialized services like Codemagic ship with Flutter preinstalled. What matters is that the gate is mandatory — a failing check blocks the merge. Pin the Flutter version in your config too, so CI can't drift from your local toolchain.

The iOS caveat: building for iOS requires macOS runners, which are the expensive, slow part of every mobile pipeline. A sensible economy is to run analysis and tests on cheap Linux runners for every PR, and only do iOS builds on release branches or tags.

Signing: the hard 20%

Signing is where every mobile pipeline earns its scars, because secrets can't live in the repo.

Android is the gentler case: base64-encode your keystore into a CI secret, decode it during the build, and feed the passwords in via environment variables referenced from key.properties. Guard the keystore itself carefully — losing an upload key is a support ticket; with Play App Signing enrolled, Google holds the final signing key so recovery is possible.

iOS means certificates and provisioning profiles. Doing this by hand in CI is misery; use tooling. Fastlane's match stores your signing assets in a private encrypted repo and syncs them onto any machine, including CI runners. Set it up once, and "new laptop" or "new CI provider" stops being a signing crisis.

Stages 2–3: build and deliver

Tag-triggered releases work well: pushing v1.4.0 kicks off the release workflow. Derive the build number from the CI run number or commit count so it always increments — the stores reject reused build numbers, and this is the single most common automated-release failure.

For delivery, Fastlane again does the boring parts: upload_to_play_store for Android (pointed at an internal track first), upload_to_testflight for iOS. Keep release notes in a file in the repo (CHANGELOG.md or fastlane's metadata folders) so they version with the code and the pipeline can submit them.

The payoff of stage 3 is bigger than saved minutes: every merge to main can produce an installable build for testers automatically. Feedback arrives days earlier, from builds that were produced by a clean, repeatable process.

Keep the human where the risk is

Full auto-promotion to production is possible and usually wrong for apps. Store releases are hard to un-ship — a bad binary rolls out to real users while you scramble. Let the robot do everything up to the store's testing track, then promote deliberately: use staged rollouts on Play (start at a small percentage and watch crash reports), and phased release on the App Store. The pipeline's job is to make the release candidate cheap and trustworthy; the release decision stays yours.

The takeaway

CI/CD for Flutter is one honest weekend for the quality gate, one annoying weekend for signing, and an afternoon for delivery — after which releases stop being events. Test on every PR, sign with match and CI secrets, auto-increment build numbers, deliver to test tracks automatically, and promote by hand. Boring, repeatable releases are what let you ship often enough to matter.