Blogs/Technology

Flutter CI/CD Workflow: A Simple Guide Using App Centre

Written byTaha
Aug 17, 2026
13 Min Read
Flutter CI/CD Workflow: A Simple Guide Using App Centre Hero
Too Long? Read This First
- Do not start a new Flutter pipeline with App Center. Its build, test and distribution services are retired.
- Run formatting, static analysis and unit or widget tests on every pull request without loading production signing secrets.
- Build Android on Linux. Use a macOS runner for iOS because Xcode is required.
- Pin the Flutter SDK and commit pubspec.lock for applications. Cloning the latest stable Flutter branch during every build is not reproducible.
- Generate an Android App Bundle (.aab) for Google Play and a signed .ipa for TestFlight or the App Store.Store keystores, certificates, API keys and passwords in protected CI secrets, not in Git or shell scripts.
- Put production deployment jobs behind a GitHub environment with branch restrictions and required reviewers.
- Use fastlane, Google Play and TestFlight for store delivery. Firebase App Distribution is a practical option for pre-release testers.
- Replace old flutter_driver tests with Flutter's integration_test package.
- Treat build, distribution and monitoring as separate capabilities. App Center bundled them; a modern pipeline does not have to.

Mobile releases often fail because of process gaps rather than application code. A developer builds with one Flutter version, another machine uses different native tooling and someone uploads an artifact that was never tested in its final configuration.

CI/CD removes much of that uncertainty by making the same checks and build commands run every time.

The original version of this guide used Microsoft Visual Studio App Center. That workflow is no longer available. App Center's Build, Test and Distribution services were retired on March 31, 2025. Analytics and Diagnostics received a temporary extension, but that ended on June 30, 2026.

So this cannot honestly remain an App Center setup tutorial. Instead, I have rebuilt it as a 2026 migration guide using GitHub Actions for orchestration and fastlane for Android and iOS delivery. The same principles also apply if your team chooses Azure Pipelines, Codemagic or Bitrise.

What Is CI/CD for a Flutter App?

CI/CD is a set of automated checks and release steps that turns a source-code change into a tested, traceable application build.

Continuous Integration (CI) validates changes as developers open pull requests or merge code. A Flutter CI job normally installs the pinned SDK, resolves packages, checks formatting, runs flutter analyze, executes automated tests, and confirms that the platform projects compile.

Continuous Delivery (CD) prepares a signed build that is ready for a tester group or store. A controlled job creates the Android App Bundle or iOS IPA, applies signing credentials, assigns a unique build number, and uploads the artifact.

Continuous deployment goes one step further by releasing automatically after all gates pass. For mobile apps, I prefer automatic upload to an internal track or TestFlight, followed by a deliberate approval before production. Apple and Google review processes also mean that “deployment” is not always the same as immediate public availability.

A useful Flutter pipeline gives us:

  • one repeatable SDK and dependency environment;
  • fast feedback on pull requests;
  • versioned Android and iOS artifacts;
  • traceability from a store build to a Git commit;
  • protected access to signing credentials; and
  • fewer manual release steps.

If you want to go deeper into quality gates beyond this Flutter example, see our guide to implementing quality assurance in a CI/CD pipeline.

Why the Old App Center Workflow No Longer Works

The previous article instructed readers to create Android and iOS apps in the App Center dashboard, connect a repository, upload signing files and add appcenter-post-clone.sh scripts.

Those steps should be removed, not lightly edited. Microsoft says App Center retired on March 31, 2025 and that users would no longer be able to sign in or call its APIs after retirement. Its temporary Analytics and Diagnostics extension ended in June 2026.

The old scripts also had technical weaknesses independent of the shutdown:

  • they cloned Flutter's moving stable branch during each build;
  • they used set -x, which can expose sensitive command data in logs;
  • they ran flutter clean on every build, defeating useful caches;
  • the Android script built an APK even though Google Play prefers App Bundles;
  • they referred to the retired flutter_driver approach; and
  • signing and distribution logic lived in a vendor-specific dashboard rather than reviewed pipeline code.

Microsoft's retirement guidance recommends Azure Pipelines for App Center Build migrations, BrowserStack App Automate for device testing, TestFlight and Google Play for distribution, and other Azure-connected services for analytics and diagnostics.

Those are valid migration targets. This guide uses GitHub Actions because its workflow can live beside the Flutter source code and is easy to review. If your organization is already standardized on Azure DevOps, the same commands can run in Azure Pipelines.

Mapping App Center Features to a 2026 Toolchain

App Center capabilityPractical replacementResponsibility
BuildGitHub Actions, Azure Pipelines, Codemagic or BitriseInstall toolchains and compile Android or iOS
Testflutter test, integration_test, emulator jobs and a device cloudValidate Dart, widgets, integrations and physical devices
DistributionGoogle Play internal testing, TestFlight or Firebase App DistributionDeliver signed builds to testers
Production releaseGoogle Play and App Store Connect, commonly automated with fastlaneUpload store artifacts and metadata
AnalyticsA supported product such as Firebase Analytics, Azure-connected monitoring or another analytics platformMeasure product usage
Crash diagnosticsFirebase Crashlytics, Sentry, Azure-connected monitoring or another diagnostics platformCapture and investigate failures
Build
Practical replacement
GitHub Actions, Azure Pipelines, Codemagic or Bitrise
Responsibility
Install toolchains and compile Android or iOS
1 of 6

Do not choose one product merely because it replaces the largest number of App Center menu items. Build orchestration, device testing, tester distribution and production monitoring have different security and operational requirements.

The Workflow We Will Build

The pipeline has two boundaries:

  1. CI on pull requests and main: format, analyze, test and perform unsigned or development builds. This workflow does not receive production credentials.
  2. CD on a version tag or manual release: obtain approval, restore signing credentials, create store artifacts and upload them to controlled release tracks.

This separation matters. Forked pull requests and unreviewed branches should never be able to read production signing secrets.

Prerequisites

Before adding the workflows, confirm that:

  • the Flutter project builds locally for Android and iOS;
  • pubspec.lock is committed for the application;
  • Android release signing is configured and the upload keystore is backed up;
  • the app already exists in Google Play Console and App Store Connect;
  • the team has an Apple Developer Program membership;
  • fastlane is initialized and tested locally before it runs in CI; and
  • your repository can use GitHub Actions and protected environments.

Xcode is required for iOS builds, so iOS jobs must run on macOS. Android CI can run on Linux, which is usually faster and less expensive than a macOS runner.

1. Pin the Flutter Version

The first App Center script cloned whatever happened to be on Flutter's stable branch. That makes a build change even when the application code has not changed.

Pin one tested Flutter SDK version. The current Flutter documentation reflects Flutter 3.44.7 as of July 31, 2026, so the example below uses it. Replace it deliberately when your project upgrades.

Add an exact Flutter constraint to pubspec.yaml:

environment:
  sdk: ">=3.0.0 <4.0.0"
  flutter: "3.44.7"

The GitHub workflow can then read the version from this file. Teams using FVM can point the setup action at .fvmrc instead.

Also commit the lockfile:

git add pubspec.yaml pubspec.lock
git commit -m "Pin Flutter and Dart dependencies"

Applications should normally commit pubspec.lock so CI resolves the same package versions developers tested. Reusable Dart or Flutter packages follow different lockfile conventions because their compatibility range must be tested more broadly.

Keep the release version and monotonically increasing build number in the same file:

version: 1.4.0+140

Flutter maps the value after + to Android's versionCode and Apple's build version. Increment it beyond the last build already uploaded through App Center or another pipeline before creating the release tag. This is safer during a migration than assuming a new CI provider's run counter starts above the existing store build number.

Let’s Build Your Flutter App Together!

Work with our expert team to turn your app idea into a fast, stunning Flutter product.

2. Add a Pull-Request CI Workflow

Create .github/workflows/flutter-ci.yml:

name: Flutter CI

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

concurrency:
  group: flutter-ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  android:
    name: Analyze, test and build Android
    runs-on: ubuntu-latest

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Set up Java
        uses: actions/setup-java@v5
        with:
          distribution: temurin
          java-version: "17"
          cache: gradle

      - name: Set up Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: stable
          flutter-version-file: pubspec.yaml
          cache: true

      - name: Install packages
        run: flutter pub get

      - name: Check formatting
        run: dart format --output=none --set-exit-if-changed .

      - name: Analyze source
        run: flutter analyze --fatal-infos

      - name: Run tests
        run: flutter test --coverage

      - name: Build development APK
        run: flutter build apk --debug

      - name: Upload Android artifact
        uses: actions/upload-artifact@v5
        with:
          name: android-debug-${{ github.sha }}
          path: build/app/outputs/flutter-apk/app-debug.apk
          if-no-files-found: error
          retention-days: 7

  ios:
    name: Compile iOS without signing
    runs-on: macos-15

    steps:
      - name: Check out repository
        uses: actions/checkout@v6

      - name: Set up Flutter
        uses: subosito/flutter-action@v2
        with:
          channel: stable
          flutter-version-file: pubspec.yaml
          cache: true

      - name: Install packages
        run: flutter pub get

      - name: Compile iOS release
        run: flutter build ios --release --no-codesign

This workflow gives every pull request four useful gates:

  • formatting must be stable;
  • static analysis must pass;
  • automated tests must pass; and
  • both platform projects must compile.

The Android artifact is a development APK for review, not a Play Store release. The iOS job intentionally compiles without signing. Production credentials remain outside the pull-request workflow.

macos-15 is pinned here instead of macos-latest to reduce surprise Xcode changes. Runner images still receive updates, so record the Xcode and Flutter versions in build logs and test upgrades deliberately.

3. Add Integration Tests Without Flutter Driver

The original article suggested Flutter Driver. Flutter now directs projects to the integration_test package.

Add it under dev_dependencies:

dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter

Place tests in integration_test/ and run them against an emulator, simulator or connected device:

flutter test integration_test

Do not add a device-dependent command to the basic Linux job and assume it will work. Create a separate job that boots the required emulator or sends the build to a device-testing provider. Keep unit and widget tests fast so developers receive early feedback, then run the slower integration suite after those gates pass.

4. Configure Android Release Signing Securely

Google Play prefers an Android App Bundle. Flutter creates it with:

flutter build appbundle --release

The output is:

build/app/outputs/bundle/release/app-release.aab

Do not commit the upload keystore or android/key.properties. Keep an encrypted backup outside CI and store the following values as protected environment secrets:

  • ANDROID_KEYSTORE_BASE64
  • ANDROID_STORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD
  • PLAY_SERVICE_ACCOUNT_JSON_BASE64

Base64 is transport encoding, not encryption. The security comes from the CI secret store and access policy.

A release job can reconstruct the files only for the duration of the runner:

- name: Restore Android signing files
  shell: bash
  env:
    KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
    STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
    KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
    KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
  run: |
    echo "$KEYSTORE_BASE64" | base64 --decode > android/app/upload-keystore.jks
    {
      echo "storePassword=$STORE_PASSWORD"
      echo "keyPassword=$KEY_PASSWORD"
      echo "keyAlias=$KEY_ALIAS"
      echo "storeFile=upload-keystore.jks"
    } > android/key.properties

- name: Build signed Android App Bundle
  run: flutter build appbundle --release

Never add set -x to a step that handles secrets. GitHub masks registered secret values, but avoiding unnecessary command tracing is a safer design.

Use Play App Signing and protect the upload key. Losing an upload key is recoverable through Google's reset process when Play App Signing is enabled, but an undocumented or unowned signing setup still causes release delays.

5. Upload Android Builds With fastlane

Flutter's official continuous-delivery guide recommends testing fastlane locally before moving it to a cloud runner.

Add fastlane to a committed Gemfile:

source "https://rubygems.org"

gem "fastlane"

Create android/fastlane/Fastfile:

default_platform(:android)

platform :android do
  desc "Upload the Flutter bundle to Google Play internal testing"
  lane :internal do
    upload_to_play_store(
      track: "internal",
      aab: "../build/app/outputs/bundle/release/app-release.aab",
      json_key: "play-store-key.json",
      skip_upload_metadata: true,
      skip_upload_images: true,
      skip_upload_screenshots: true
    )
  end
end

In the release workflow, decode the Google Play service-account key and run the lane:

- name: Set up Ruby
  uses: ruby/setup-ruby@v1
  with:
    bundler-cache: true

- name: Restore Google Play credentials
  env:
    PLAY_KEY_BASE64: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON_BASE64 }}
  run: echo "$PLAY_KEY_BASE64" | base64 --decode > android/play-store-key.json

- name: Upload to Google Play internal testing
  run: cd android && bundle exec fastlane internal

Use the internal testing track first. Promotion from internal to production should be a separate, reviewable action rather than an automatic side effect of compiling the app.

6. Build and Upload iOS Through TestFlight

iOS delivery has two independent security requirements:

  1. Code signing: a distribution certificate and provisioning profile, or a managed signing workflow such as fastlane match.
  2. App Store Connect authentication: preferably an App Store Connect API key for CI uploads.

The API key can authorize an upload, but it does not sign the application. Configure and test signing locally before automating it.

Once signing is available on the macOS runner, Flutter can create the IPA:

flutter build ipa --release

The generated file is placed under build/ios/ipa/.

An iOS fastlane lane can upload that artifact to TestFlight:

default_platform(:ios)

platform :ios do
  desc "Upload the signed Flutter IPA to TestFlight"
  lane :beta do
    api_key = app_store_connect_api_key(
      key_id: ENV.fetch("ASC_KEY_ID"),
      issuer_id: ENV.fetch("ASC_ISSUER_ID"),
      key_content: ENV.fetch("ASC_KEY_BASE64"),
      is_key_content_base64: true
    )

    ipa = Dir["../build/ios/ipa/*.ipa"].first
    UI.user_error!("No IPA found") unless ipa

    upload_to_testflight(
      api_key: api_key,
      ipa: ipa,
      skip_waiting_for_build_processing: true
    )
  end
end

Run that lane from the ios directory after building:

- name: Upload to TestFlight
  env:
    ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
    ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
    ASC_KEY_BASE64: ${{ secrets.ASC_KEY_BASE64 }}
  run: cd ios && bundle exec fastlane beta

Store signing material and App Store Connect credentials in a protected production environment. Do not expose them to pull-request jobs.

7. Protect the Release Workflow

Triggering production delivery on every push is fast, but it is rarely the safest first implementation.

A controlled release workflow can run on a semantic version tag and also support a manual trigger:

name: Flutter Release

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:

permissions:
  contents: read

jobs:
  release:
    environment: production
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      # Restore tools and signing files, build, then upload.

Configure the production environment with:

  • required reviewers;
  • permitted deployment branches or tags;
  • environment-level secrets;
  • a rule preventing self-approval where appropriate; and
  • a concurrency policy that prevents overlapping releases.

Let’s Build Your Flutter App Together!

Work with our expert team to turn your app idea into a fast, stunning Flutter product.

GitHub does not expose environment secrets to the job until its protection rules pass. That makes the environment a meaningful security boundary rather than a label.

For stronger supply-chain controls, pin third-party actions to reviewed commit SHAs and use an update process such as Dependabot to propose changes. Major-version tags make examples readable, but an immutable SHA gives production workflows a stronger guarantee about the code being executed.

8. Replace App Center Distribution for Testers

App Center Distribution used to provide one tester portal for both platforms. In 2026, the simplest supported choices are:

  • Google Play internal testing for Android builds close to production;
  • TestFlight internal or external testing for iOS;
  • Firebase App Distribution for cross-platform pre-release groups; or
  • an enterprise mobile-device-management platform for internal corporate apps.

Firebase App Distribution can accept builds through its CLI, fastlane or Gradle. For example, after producing a signed Android APK:

firebase appdistribution:distribute \
  build/app/outputs/flutter-apk/app-release.apk \
  --app "$FIREBASE_ANDROID_APP_ID" \
  --groups "qa-team" \
  --release-notes "Commit $GITHUB_SHA"

This is a distribution step, not a replacement for CI. The app must still be built, signed and tested before it is uploaded.

9. Notifications and Observability

A failed build should notify the people who can act on it. GitHub already exposes pull-request checks and can send platform notifications. Slack or Microsoft Teams notifications can be added through approved integrations or incoming webhooks.

Avoid sending an alert for every successful step. Useful notifications normally include:

  • failed main builds;
  • failed production releases;
  • releases waiting for approval;
  • successful uploads to a tester track; and
  • production crash or regression alerts from the monitoring platform.

CI logs are not application monitoring. Replace App Center Analytics and Diagnostics separately, confirm that the new SDK respects consent and privacy requirements, and verify that symbols or mapping files are uploaded so production crashes are readable.

App Center Migration Checklist

If the project previously used App Center, work through this migration deliberately:

  1. Inventory every App Center capability the app depended on: build, test, distribution, analytics, crashes, push or CodePush.
  2. Recover signing files, tester lists, build variables and historical configuration from organizational backups or retained documentation.
  3. Rotate credentials that were stored in App Center and add the replacements to the new secret store.
  4. Remove appcenter-post-clone.sh and other dead build hooks after the new pipeline is proven.
  5. Remove or replace retired App Center SDK integrations in the application.
  6. Migrate Flutter Driver tests to integration_test.
  7. Reproduce Android and iOS release builds locally with the pinned Flutter version.
  8. Compare application IDs, entitlements, signing identities, version numbers and artifact formats with the last known release.
  9. Upload first to an internal Play track and TestFlight group.
  10. Document ownership, renewal and recovery for every certificate, key and service account.

Do not delete the old release documentation until the new pipeline has produced installable, signed builds on both platforms.

Common Flutter CI/CD Mistakes

MistakeWhy it causes problemsBetter approach
Cloning Flutter's latest stable branch during each buildThe toolchain changes without a source commitPin an exact tested Flutter version
Running release jobs on pull requestsUntrusted code may reach signing secretsKeep CI secret-free and release only from protected refs
Committing a keystore or .p12 fileRepository access becomes signing accessUse encrypted CI secrets and an external recovery backup
Building only an APK for Google PlayAPKs miss App Bundle delivery benefitsBuild a signed .aab for Play releases
Using flutter_driver in a new pipelineThe project has moved to integration_testMigrate the tests before automating them
Using one build number repeatedlyStores reject or confuse duplicate uploadsGenerate a monotonically increasing build number
Uploading directly to productionA successful compile is not release approvalUse internal tracks, TestFlight and protected promotion
Treating analytics as part of the build toolIt couples unrelated lifecycle decisionsSelect and migrate monitoring separately
Cloning Flutter's latest stable branch during each build
Why it causes problems
The toolchain changes without a source commit
Better approach
Pin an exact tested Flutter version
1 of 8

Choosing a Flutter CI/CD Platform

GitHub Actions is not the only valid App Center replacement.

PlatformBest fitTrade-off
GitHub ActionsTeams already hosting code on GitHub and wanting pipeline-as-codeiOS signing and store delivery require deliberate setup
Azure PipelinesOrganizations standardized on Azure DevOps or following Microsoft's migration pathFlutter-specific conveniences may require more scripting
CodemagicFlutter teams wanting managed mobile workflows and signing assistanceAdds a specialist CI vendor and pricing model
BitriseMobile teams wanting visual workflows and broad mobile integrationsComplex workflows can become platform-specific
Xcode CloudApple-focused teams needing native Xcode and App Store integrationIt does not replace the Android pipeline
GitHub Actions
Best fit
Teams already hosting code on GitHub and wanting pipeline-as-code
Trade-off
iOS signing and store delivery require deliberate setup
1 of 5

Choose based on runner availability, signing support, secret controls, auditability, build minutes and the team's ability to maintain the pipeline. The Flutter commands should remain portable even if the orchestration provider changes.

Conclusion

App Center once made Flutter build and tester distribution approachable, but it is no longer a usable foundation in 2026. Preserving its dashboard instructions would create a polished guide to a dead end.

A current Flutter CI/CD workflow should keep the important ideas while removing the vendor dependency: pin the Flutter SDK, validate every pull request, compile both platform projects, isolate production secrets, create the correct store artifacts and release through protected environments.

GitHub Actions and fastlane are one practical combination. Azure Pipelines, Codemagic and Bitrise can implement the same lifecycle. The durable part is not the logo on the CI dashboard; it is the reviewed, reproducible path from a commit to a signed release.

Frequently Asked Questions

Can I still use Microsoft App Center for Flutter CI/CD?

No. App Center's build, test and distribution capabilities retired in March 2025. Its extended Analytics and Diagnostics support ended in June 2026, so new Flutter pipelines need supported replacements today.

What is the best App Center replacement for Flutter?

There is no universal replacement for teams. GitHub Actions offers flexible pipeline-as-code, Azure Pipelines follows Microsoft's migration path, while Codemagic and Bitrise provide more specialized mobile and Flutter workflow features.

Do Flutter iOS builds require a macOS CI runner?

Yes. Flutter iOS release builds require Xcode, which runs on macOS. Android jobs can run on Linux, but signed IPA generation and validation must use a compatible macOS environment today.

Where should Flutter signing credentials be stored?

Store keystores, certificates, provisioning profiles, passwords and API keys in protected CI environment secrets. Keep encrypted recovery backups separately, restrict release access and never commit credentials to Git under any circumstances.

Should every merge automatically publish the Flutter app?

Usually not at first. Automate testing and internal-track uploads, then require approval before production promotion by default. This preserves delivery speed while protecting signing credentials and preventing accidental public releases.

Author-Taha

Flutter Dev @ F22 Labs, solving mobile app challenges with a cup of coffee and a passion for crafting elegant solutions. Let's build something amazing together!

Share this article

Phone

Next for you

8 Best GraphQL Libraries for Node.js in 2025 Cover

Technology

Aug 4, 202613 min read

8 Best GraphQL Libraries for Node.js in 2025

8 Best GraphQL Libraries for Node.js in 2026 Too Long? Read This First - Choose Apollo Server when you need a mature ecosystem, GraphOS integration, plugins, or Apollo Federation. - Choose GraphQL Yoga for a modern, portable server with Fetch API compatibility and built-in support for subscriptions over Server-Sent Events. - Choose Mercurius when your application already uses Fastify and runtime efficiency is a major priority. - Use GraphQL.js when you need the official JavaScript implementati

9 React Native Animation Libraries and Tools Compared Cover

Technology

Aug 4, 202615 min read

9 React Native Animation Libraries and Tools Compared

Too Long? Read This First - Use React Native Reanimated for gesture-driven, interruptible, and performance-sensitive interface animations. - Use the built-in Animated API for simple fades, transforms, and timed sequences without another dependency. - Pair React Native Gesture Handler with Reanimated for swipes, dragging, pinching, rotation, and other touch-driven experiences. - Use Lottie React Native for non-interactive motion graphics supplied by designers. - Choose React Native Skia for cust

9 Critical Practices for Secure Web Application Development Cover

Technology

Aug 4, 202616 min read

9 Critical Practices for Secure Web Application Development

Too Long? Read This First - Define security requirements and model threats before implementation begins. - Treat authentication, account recovery, and MFA as one complete identity system. - Apply server-side authorization to every protected action and object. - Prevent injection with parameterized APIs, structured validation, safe output handling, and restricted outbound requests. - Protect sessions and tokens throughout their complete lifecycle. - Minimise sensitive data and manage encryption