How to Set Up Android CI/CD with GitHub Actions — Firebase Distribution & Play Store
Source: Dev.to
CI/CD for Android Apps on GitHub Actions
Setting up CI/CD for Android apps on GitHub Actions is straightforward once you know the gotchas. This guide covers everything:
- Building signed APK / AAB files
- Caching Gradle
- Deploying to Firebase App Distribution for testers
- Publishing to the Google Play Store
Prerequisites
| Requirement | Details |
|---|---|
| Android project | Gradle (Groovy or Kotlin DSL) |
| Repository | GitHub repo |
| Firebase | Firebase project with your app added |
| Play Store | Google Play Console account with your app set up |
Common Pitfall
The gradlew file may not be executable in Git. Fix it once in the repo:
git update-index --chmod=+x gradlew
git commit -m "Make gradlew executable"
Or add a step to your workflow (shown later).
Create a Signing Keystore (if you don’t have one)
keytool -genkeypair -v \
-keystore release.jks \
-keyalg RSA -keysize 2048 \
-validity 10000 \
-alias release \
-storepass YOUR_STORE_PASSWORD \
-keypass YOUR_KEY_PASSWORD \
-dname "CN=Your Name, O=Your Org"
Never commit the keystore to Git. Instead, base‑64 encode it:
-
macOS
base64 -i release.jks | pbcopy # copies to clipboard -
Linux
base64 -w 0 release.jks
Add Secrets to GitHub
Navigate to Repo → Settings → Secrets and variables → Actions and create the following secrets.
General signing secrets
| Secret | Value |
|---|---|
KEYSTORE_BASE64 | Base64‑encoded .jks file |
KEYSTORE_PASSWORD | Your keystore password |
KEY_ALIAS | Your key alias (e.g., release) |
KEY_PASSWORD | Your key password |
Firebase Distribution secrets
| Secret | Value |
|---|---|
FIREBASE_APP_ID | Firebase Console → Project Settings → Your Android app ID |
FIREBASE_SERVICE_ACCOUNT | JSON content of a Firebase service‑account key |
Play Store secrets
| Secret | Value |
|---|---|
PLAY_SERVICE_ACCOUNT_JSON | Google Play Console → API access → Service account JSON |
Workflow 1 – Build & Deploy to Firebase
Create .github/workflows/android-firebase.yml:
name: Android — Build & Deploy to Firebase
on:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build & Upload to Firebase
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Make gradlew executable
run: chmod +x ./gradlew
- name: Decode Keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/release.jks
- name: Build Release APK
run: ./gradlew assembleRelease
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- name: Upload to Firebase Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
groups: testers
file: app/build/outputs/apk/release/app-release.apk
Workflow 2 – Build & Publish to Play Store
Create .github/workflows/android-playstore.yml:
name: Android — Build & Publish to Play Store
on:
push:
tags:
- 'v*' # e.g. v1.0.0
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build & Publish to Play Store
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Make gradlew executable
run: chmod +x ./gradlew
- name: Decode Keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/release.jks
- name: Build Release AAB
run: ./gradlew bundleRelease
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- name: Upload to Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: com.yourcompany.yourapp # ← replace with your package name
releaseFiles: app/build/outputs/bundle/release/app-release.aab
track: internal # internal / alpha / beta / production
Note: Replace
com.yourcompany.yourappwith your actual package name. Thetrackcan beinternal,alpha,beta, orproduction.
Gradle Signing Configuration
Add the following to app/build.gradle.kts (or the Groovy equivalent) so the build picks up the secrets from the environment:
android {
signingConfigs {
create("release") {
storeFile = file("release.jks")
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
Frequently Encountered Errors
| Error | Fix |
|---|---|
Permission denied: gradlew | Ensure the workflow runs chmod +x ./gradlew or make the file executable in Git (git update-index --chmod=+x gradlew). |
JAVA_HOME is not set | The setup-java step must appear before any Gradle commands. |
| Build takes too long | The Gradle cache step (actions/cache@v4) dramatically reduces build times. |
You’re now ready to automate Android builds, testing, and releases with GitHub Actions! 🚀
Common CI/CD Issues & Tips for Android Projects
- First run latency – The initial build downloads all dependencies and can take 5‑10 minutes.
- “No key with alias found in keystore” – Double‑check that your
KEY_ALIASsecret matches exactly the alias you used when creating the keystore. - “Failed to read key from store” – Your Base64‑encoded keystore may be corrupted. Re‑encode the keystore file and update the secret.
Concurrency & Triggers
- Concurrency groups prevent wasted CI minutes. If you push twice quickly, the first run is automatically cancelled.
- Tag‑based triggers for the Play Store are ideal – push a
v1.0.0tag when you’re ready to release, and the CI pipeline handles the rest.
Distribution Options
- Firebase App Distribution – Great for internal testing. Testers receive a notification with each new build, and no Play Store review is required.
Visual Workflow Generator
- Run Lane – A free visual configurator that generates GitHub Actions workflows for Android and iOS.
- Pick your platform.
- Choose your distribution target.
- Download a ready‑to‑use workflow.
No account needed.
Tags: android, github, cicd, kotlin