Whether you're building MVPs, shipping frequent updates, or maintaining stable production releases, setting up a solid CI/CD pipeline can save hours of manual work and eliminate human error. In this guide, we’ll walk through how to fully automate the build and deployment process of your Flutter Android app using Fastlane and GitHub Actions. If you’re looking to set up CI/CD for Flutter iOS apps, we’ve covered that in detail in a separate guide - check it out here.
From Firebase App Distribution for development builds to Google Play Store releases for production, you’ll learn how to set up an efficient, developer-friendly workflow from start to finish.
What you’ll need for flutter android CI/CD setup
Before you get started, make sure you have the following:
- A Flutter project with Android support
- A GitHub repository to host your project
- A Google Play Developer account (for publishing production builds)
- A Firebase project (for distributing development builds to testers)
Step 1: Install Ruby on mac
Let's install the latest ruby stable version:
brew install ruby
echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc
After installation, restart your terminal or run source ~/.zshrc to apply the changes.
Step 2: Install Fastlane for Flutter Android CI/CD
Fastlane is a tool that automates building and releasing iOS and Android apps. Install it using Homebrew:
brew install fastlane
Step 3: Initialize Fastlane in your Flutter android project
Navigate to your project's android directory and initialize Fastlane:
cd android
fastlane init
During initialization:
- Provide the package name for your application when asked (e.g. io.fabric.yourapp)
- Press enter when asked for the path to your json secret file
- Answer 'n' when asked if you plan on uploading info to Google Play via fastlane (we can set this up later)
After completion, you should see a new fastlane folder Gemfile in your android directory.
Step 4: Setup supply
Supply is a fastlane tool that uploads app metadata, screenshots, and binaries to Google Play. You can also select tracks for builds and promote builds to production!
For supply to be able to initialize, you need to have successfully uploaded an APK to your app in the Google Play Console at least once.
Setting it up requires downloading a credentials file from your Google Developers Service Account.
- Open the Google Play Console
Click Account Details, and note the Google Cloud Project ID listed there
- Enable the Google Play Developer API by selecting an existing Google Cloud Project that fits your needs and pushing ENABLE
If you don't have an existing project or prefer to have a dedicated one for fastlane, create a new one here and follow the instructions
- Open Service Accounts on Google Cloud and select the project you'd like to use
- Click the CREATE SERVICE ACCOUNT button at the top of the Google Cloud Platform Console page
- Verify that you are on the correct Google Cloud Platform Project by looking for the Google Cloud Project ID from earlier within the light gray text in the second input, preceding .iam.gserviceaccount.com , or by checking the project name in the navigation bar. If not, open the picker in the top navigation bar, and find the right one.
- Provide a Service account name (e.g. fastlane-supply)
- Copy the generated email address that is noted below the Service account-ID field for later use
- Click DONE (don't click CREATE AND CONTINUE as the optional steps such as granting access are not needed):
- Click on the Actions vertical three-dot icon of the service account you just created
- Select Manage keys on the menu
- Click ADD KEY → Create New Key
- Make sure JSON is selected as the Key type , and click CREATE
- Save the file on your computer when prompted and remember where it was saved at
- Open the Google Play Console and select Users and Permissions
- Click Invite new users
- Paste the email address you saved for later use into the email address field
- Click on Account Permissions
- Choose the permissions you'd like this account to have. We recommend Admin (all permissions), but you may want to manually select all checkboxes and leave out some of the Releases permissions such as Release to production, exclude devices, and use Play App Signing
- Click on Invite User
- Test Your API Connection
fastlane run validate_play_store_json_key
json_key:/path/to/your/supply.json - Configure the Appfile Create or edit the file android/fastlane/Appfile with the following:
- content: json_key_file("fastlane/supply.json")
- package_name("app.package.name")
- Download Existing Metadata (if your app is already on Google Play) This will download your app's metadata to fastlane/metadata/android
fastlane supply init
Step 5: Configure firebase distribution
Add the Firebase App Distribution plugin to Fastlane:
fastlane add_plugin firebase_app_distribution
Next:
- Create and configure a Firebase project for your android app
- Generate a service account file
- Save the service account JSON file to /android/fastlane/service-account.json
Step 6: Update your fastfile
Edit android/fastlane/Fastfile with the following content:
1default_platform(:android)
2
3platform :android do
4 desc "Push a new development build to Firebase"
5 lane :development do |options|
6 # Handle input parameters
7 version = options[:version] || "1.0.0"
8 build_number = options[:build_number] || 1
9 release_notes = options[:release_notes] || "Lots of amazing new features to test out!"
10 groups = options[:groups] || "Internal"
11 testers = options[:testers] || ""
12
13 # Clean and build the app
14 sh "flutter clean"
15 sh "flutter build apk --build-name=#{version} --build-number=#{build_number} --flavor development --target=lib/main_development.dart"
16
17 # Upload to Firebase App Distribution
18 firebase_app_distribution(
19 app: "YOUR_FIREBASE_APP_ID",
20 service_credentials_file: "fastlane/service-account.json",
21 apk_path: "../build/app/outputs/flutter-apk/app-development-release.apk",
22 testers: testers,
23 groups: groups,
24 release_notes: release_notes,
25 )
26 end
27
28 desc "Push a new production build to Google Play"
29 lane :production do |options|
30 # Handle input parameters
31 version = options[:version] || "1.0.0"
32 build_number = options[:build_number] || 1
33 release_notes = options[:release_notes] || "Lots of amazing new features to test out!"
34
35 # Create changelog file
36 changelog_path = "metadata/android/en-US/changelogs/#{build_number}.txt"
37 FileUtils.mkdir_p(File.dirname(changelog_path))
38 File.write(changelog_path, release_notes)
39
40 # Clean and build the app bundle
41 sh "flutter clean"
42 sh "flutter build appbundle --build-name=#{version} --build-number=#{build_number} --flavor production --target lib/main_production.dart"
43
44 # Upload to Google Play Store
45 # Tracks: "internal", "alpha", "beta", or "production"
46 upload_to_play_store(
47 track: "internal",
48 aab: "../build/app/outputs/bundle/productionRelease/app-production-release.aab",
49 key: "fastlane/supply.json",
50 metadata_path: "fastlane/metadata/android",
51 )
52 end
53end
Replace YOUR_FIREBASE_APP_ID with your actual Firebase App ID. Also, ensure that file paths match your project's structure.
Step 7: Set up Android app signing
For publishing to the Google Play Store, you need a signing key:
- Generate a release keystore file.
- Create key.properties file Create a file at android/key.properties with: storePassword=YOUR_KEYSTORE_PASSWORD
keyPassword=YOUR_KEY_PASSWORD
keyAlias=upload
storeFile=release.keystore - Configure Gradle for signing Update your android/app/build.gradle file to use the signing configuration. Add this near the top: And in the android block, add:
1signingConfigs {
2 release{
3 keyAlias keystoreProperties['keyAlias']
4 keyPassword keystoreProperties['keyPassword']
5 storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
6 storePassword keystoreProperties['storePassword']
7 }
8}
9
10flavorDimensions "default"
11productFlavors {
12 production {
13 dimension "default"
14 applicationIdSuffix ""
15 manifestPlaceholders = [appName: "<<app-name>>"]
16 signingConfig signingConfigs.release // Always use release signing for production
17 }
18 development {
19 dimension "default"
20 applicationIdSuffix ".dev"
21 manifestPlaceholders = [appName: "<<[DEV] app-name>>"]
22 signingConfig signingConfigs.debug
23 }
24}
25
26buildTypes {
27 release {
28 if (project.hasProperty('target') && project.property('target').toString().equals('lib/main_production.dart')) {
29 println "Using release signing configuration"
30 signingConfig signingConfigs.release
31 } else if (project.hasProperty('target') && project.property('target').toString().equals('lib/main_development.dart')) {
32 println "Using debug signing configuration"
33 signingConfig signingConfigs.debug
34 } else {
35 println "Using debug signing configuration"
36 signingConfig signingConfigs.debug
37 }
38 minifyEnabled true
39 proguardFiles getDefaultProguardFile('proguard-android.txt')
40 }
41}
Step 8: Setup testing groups
- In Firebase Console, create tester groups for your development builds
- In Google Play Console, set up closed testing tracks for beta testers
Step 9: Test locally
Before setting up GitHub Actions, test your Fastlane setup locally:
cd android
fastlane production version:"1.0.0" build_number:1 release_notes:"First release"
fastlane development version:"1.0.0" build_number:1 testers:"email@example.com" groups:"<<tester_group>>" release_notes:"First release"
Step 10: Set up GitHub actions workflows for CI/CD
Create the following workflow files in your repository's .github/workflows directory:
Main Workflow (main.yaml)
This workflow runs tests when code is pushed to the main branch or when a pull request is created:
1name: Flutter CI Tests
2on:
3 push:
4 branches:
5 - main
6 pull_request:
7 branches:
8 - main
9jobs:
10 test:
11 name: Run Flutter Tests
12 runs-on: ubuntu-latest
13 steps:
14 - uses: actions/checkout@v3
15 - uses: subosito/flutter-action@v2
16 with:
17 flutter-version: "3.24.5"
18 channel: "stable"
19 - run: flutter --version
20 - name: Get Packages
21 run: flutter pub get
22 - name: Run Tests
23 run: flutter test
Development workflow (deploy_android_development.yaml)
1name: Deploy Android App to Development
2on:
3 workflow_dispatch:
4 inputs:
5 version:
6 description: "App Version (e.g., 1.0.0)"
7 required: true
8 default: "1.0.0"
9 build_number:
10 description: "Build Number (e.g., 42)"
11 required: true
12 default: "1"
13 release_notes:
14 description: "Release Notes"
15 required: true
16 default: "Development Build"
17 groups:
18 description: "Firebase Tester Groups"
19 required: true
20 default: "Internal"
21 testers:
22 description: "Firebase Testers"
23 required: false
24 default: ""
25jobs:
26 deployAndroidDevelopment:
27 runs-on: ubuntu-latest
28 env:
29 FIREBASE_SERVICE_ACCOUNT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
30 steps:
31 - name: Checkout repository
32 uses: actions/checkout@v3
33 - name: Setup Flutter
34 uses: subosito/flutter-action@v2
35 with:
36 flutter-version: "3.24.5"
37 channel: "stable"
38 cache: true
39 id: flutter
40 - name: Get Flutter Dependencies
41 run: flutter pub get
42 - name: Setup Ruby and Fastlane
43 uses: ruby/setup-ruby@v1
44 with:
45 ruby-version: "3.3"
46 bundler-cache: true
47 working-directory: android
48 - name: Set up Firebase authentication
49 run: echo "$FIREBASE_SERVICE_ACCOUNT" > android/fastlane/service-account.json
50 - name: Build and Deploy to Firebase App Distribution
51 run: |
52 cd ./android
53 bundle exec fastlane android development \
54 version:${{ github.event.inputs.version }} \
55 build_number:${{ github.event.inputs.build_number }} \
56 release_notes:"${{ github.event.inputs.release_notes }}" \
57 groups:${{ github.event.inputs.groups }} \
58 testers:${{ github.event.inputs.testers }}
Production workflow (deploy_android_production.yaml)
1name: Deploy Android App to Production
2on:
3 workflow_dispatch:
4 inputs:
5 version:
6 description: "App Version (e.g., 1.0.0)"
7 required: true
8 default: "1.0.0"
9 build_number:
10 description: "Build Number (e.g., 42)"
11 required: true
12 default: "1"
13 release_notes:
14 description: "Release Notes"
15 required: true
16 default: "Production Release"
17jobs:
18 deployAndroidProduction:
19 runs-on: ubuntu-latest
20 env:
21 FIREBASE_SERVICE_ACCOUNT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
22 RELEASE_SIGNING_KEY: ${{ secrets.RELEASE_SIGNING_KEY }}
23 RELEASE_KEYSTORE_PROPERTIES: ${{ secrets.RELEASE_KEYSTORE_PROPERTIES }}
24 SUPPLY_KEY: ${{ secrets.SUPPLY_KEY }}
25 steps:
26 - name: Checkout repository
27 uses: actions/checkout@v3
28 - name: Setup Java
29 uses: actions/setup-java@v1
30 with:
31 java-version: "17"
32 - name: Setup Flutter
33 uses: subosito/flutter-action@v2
34 with:
35 flutter-version: "3.24.5"
36 channel: "stable"
37 cache: true
38 id: flutter
39 - name: Setup Ruby and Fastlane
40 uses: ruby/setup-ruby@v1
41 with:
42 ruby-version: "3.3"
43 bundler-cache: true
44 working-directory: android
45 - name: Configure Keystore
46 run: |
47 echo "$RELEASE_SIGNING_KEY" > release.jks.b64
48 base64 -d -i release.jks.b64 > android/app/release.keystore
49 - name: Set up Supply Key file
50 working-directory: android
51 run: echo "$SUPPLY_KEY" > fastlane/supply.json
52 - name: Setup store key properties
53 working-directory: android
54 run: echo "$RELEASE_KEYSTORE_PROPERTIES" > key.properties
55 - name: Ensure Fastlane Metadata Directory Exists
56 working-directory: android
57 run: mkdir -p fastlane/metadata/android/en-US/changelogs
58 - name: Build and Deploy to Play Store
59 working-directory: android
60 run: |
61 bundle exec fastlane android production \
62 version:${{ github.event.inputs.version }} \
63 build_number:${{ github.event.inputs.build_number }} \
64 release_notes:"${{ github.event.inputs.release_notes }}"
Step 11: Add GitHub secrets
In your GitHub repository settings, add the following secrets:
- FIREBASE_SERVICE_ACCOUNT: The contents of your Firebase service account JSON file
- RELEASE_SIGNING_KEY: Base64-encoded contents of your keystore file
base64 -i path/to/your.keystore -o output.txt
- RELEASE_KEYSTORE_PROPERTIES: Contents of your key.properties file
- SUPPLY_KEY: Contents of your Google Play service account JSON file (supply.json)
Step 12: Test your CI/CD pipeline
Commit and push your changes to GitHub, then:
- Go to the "Actions" tab in your GitHub repository
- Select the "Deploy Android App to Development" workflow
- Click "Run workflow" and fill out the required parameters
- Monitor the workflow execution
If everything is set up correctly, your app should be automatically built and deployed to Firebase App Distribution. Once you're ready for a production release, run the "Deploy Android App to Production" workflow to send the app to Google Play.
Conclusion
With your CI/CD pipeline up and running, you’ve taken a major step toward delivering faster, more reliable Android releases. From automatically distributing test builds to your team via Firebase to pushing production-ready versions to the Play Store with confidence, your app’s release process is now streamlined and scalable.
This setup is especially powerful when paired with the right partner. Looking to set up or scale your Flutter app delivery pipeline? Explore our Flutter app development services or contact us to get started.