CI/CD Tutorial: Fastlane + GitHub Actions for Flutter Android
Overview
The pipeline does the following on every manual trigger:
- Decodes secrets (keystore, service account) from GitHub Secrets
- Sets up Java, Flutter, and Ruby environments
- Runs Fastlane to auto-increment the build number, build the AAB + APK, and upload to Play Store (internal track)
- Uploads artifacts to GitHub Actions
- Creates a GitHub Release with the AAB and APK attached
Prerequisites
- A Flutter Android project
- A Google Play Console account with your app created (at least one manual upload done)
- A Google Cloud Platform (GCP) project
- A signing keystore for your Android app
- Ruby installed locally (for running fastlane init)
Step 1: Create a Keystore for App Signing
If you don't already have a keystore, generate one:
keytool -genkey -v -keystore release-keystore.jks -alias your-key-alias -keyalg RSA -keysize 2048 -validity 10000
Keep note of:
- storePassword — the keystore password
- keyPassword — the key password
- keyAlias — the alias you chose
Do not commit the .jks file. Add it to .gitignore.
Step 2: Configure key.properties in Android
Create android/key.properties (also add to .gitignore):
storePassword=YOUR_STORE_PASSWORD keyPassword=YOUR_KEY_PASSWORD keyAlias=YOUR_KEY_ALIAS storeFile=release-keystore.jks
In android/app/build.gradle.kts, load and use this file for signing:
val keystorePropertiesFile = rootProject.file("key.properties") val keystoreProperties = Properties() if (keystorePropertiesFile.exists()) { keystoreProperties.load(FileInputStream(keystorePropertiesFile)) }
signingConfigs { create("release") { keyAlias = keystoreProperties.getProperty("keyAlias") keyPassword = keystoreProperties.getProperty("keyPassword") storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) } storePassword = keystoreProperties.getProperty("storePassword") } }
buildTypes { release { signingConfig = signingConfigs.getByName("release") } }
Step 3: Set Up GCP Service Account
Fastlane uses a Google Play API service account to upload builds.
- Go to Google Cloud Console
- Create a new project (or use an existing one)
- Enable the Google Play Android Developer API
- Go to IAM & Admin → Service Accounts → Create Service Account
- Give it a name (e.g., fastlane-cicd)
- Grant it no roles at the project level
- Create and download a JSON key for this service account
Grant Access in Play Console
- Open Google Play Console
- Go to Setup → API access
- Link to the GCP project you created
- Find your service account, click Grant Access
- Assign the Release Manager role
Step 4: Initialize Fastlane
From the android/ directory:
cd android fastlane init
When prompted, choose Google Play as the deployment target and provide your package name.
This generates:
- android/Gemfile
- android/fastlane/Appfile
- android/fastlane/Fastfile
Step 5: Configure Fastlane Files
android/Gemfile
source "https://rubygems.org" gem "fastlane"
android/fastlane/Appfile
json_key_file("~/fastlane/service_account.json") package_name("com.example.yourapp")
android/fastlane/Fastfile
Fastlane automatically:
- Fetches the highest version code across all tracks
- Increments build number automatically
- Builds APK and AAB files
- Uploads to internal testing track
- Writes build_number.txt for GitHub Releases
Key points:
- No manual version bumping needed
- Internal track upload by default
- Faster uploads by skipping metadata/images/screenshots
Step 6: Add GitHub Secrets
Go to GitHub → Settings → Secrets and variables → Actions.
Add:
- ANDROID_KEYSTORE
- SERVICE_ACCOUNT
- KEYSTORE_PASSWORD
- KEY_PASSWORD
- KEY_ALIAS
Use Base64 encoding for keystore and service account files.
Step 7: GitHub Actions Workflow
Create .github/workflows/deploy_android.yml.
The workflow:
- Checks out repository
- Sets up Java, Flutter, and Ruby
- Decodes secrets
- Runs Fastlane deploy
- Uploads APK/AAB artifacts
- Creates GitHub Release automatically
Notable Decisions
- workflow_dispatch allows manual release triggering
- continue-on-error keeps artifact upload running even if Play Store upload fails
- permissions: contents: write is required for GitHub Releases
- working-directory: android ensures Fastlane runs correctly
File Structure Summary
project-root/ ├── .github/ │ └── workflows/ │ └── deploy_android.yml ├── android/ │ ├── Gemfile │ ├── Gemfile.lock │ ├── key.properties │ ├── app/ │ │ ├── build.gradle.kts │ │ └── release-keystore.jks │ └── fastlane/ │ ├── Appfile │ └── Fastfile └── pubspec.yaml
Triggering a Release
- Go to GitHub Actions
- Select Deploy Flutter Android
- Click Run workflow
The pipeline will:
- Auto-detect next versionCode
- Build APK and AAB
- Upload to Play Store internal track
- Create GitHub Release
Supply (upload_to_play_store) Reference
Available tracks:
- internal
- alpha
- beta
- production
Key parameters:
- track
- aab
- rollout
- release_status
- track_promote_to
Fastlane also supports:
- Gradual rollouts
- Track promotion
- Metadata downloads
- Screenshot management
Important: First Upload Must Be Manual
The Play Store API cannot push to an app that has never had a build uploaded manually through Play Console.
Common Issues
Google Api Error
Only releases with status draft may be created on draft app.
Solution: Complete at least one manual Play Store upload first.
Version code already exists
Re-run the workflow to get a fresh build number.
base64: invalid input
Encode files without line breaks:
- Linux: base64 -w 0 file
- macOS: base64 -i file
Conclusion
Using Fastlane with GitHub Actions creates a powerful CI/CD pipeline for Flutter Android apps. It automates versioning, building, Play Store deployment, artifact uploads, and GitHub Releases while reducing manual work and deployment errors.