GitLab (CI+ CD) Create Android Signed build + Upload build on firebase distribution.
When we heard CI-CD, there is some reluctance in ourselves until we fully know about it!
CI- Continuous Integration, CD- Continuous Delivery.

Step1- .gitlab-ci.yml
Open GitLab and create a file in the root folder. We will write our CI-CD code in this file.
FYI: GitLab CI/CD pipelines are configured using a YAML file called .gitlab-ci.yml
within each project.
The .gitlab-ci.yml
the file defines the structure and order of the pipelines and determines:
- What to execute using GitLab Runner.
- What decisions to make when specific conditions are encountered. For example, when a process succeeds or fails.
Step-2 Caching
Caching the Gradle folder between builds may reduce building time. When we add cache to our builds, each cached job will download and extract the cache at the beginning and upload the cache at the end. Add this in .gitlab-ci.yml
at the top of the file.
before_script:
- export GRADLE_USER_HOME=$(pwd)/.gradle
- chmod +x ./gradlew
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
Step-3 Stages
Defines a job stage for our GitLab CI pipeline. It will define the order of the job. Jobs in the same stage will run in parallel. Add this after caching in the .gitlab-ci.yml file:
stages:
- build
- deploy
Step-4 Stage build
assembleDebug:
image: jangrewe/gitlab-ci-android
stage: build
only:
- master
script:
- echo $KEYSTORE_FILE | base64 -d > my.keystore
- ./gradlew assembleRelease
-Pandroid.injected.signing.store.file=$(pwd)/my.keystore
-Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD
-Pandroid.injected.signing.key.alias=$KEY_ALIAS
-Pandroid.injected.signing.key.password=$KEY_PASSWORD
- cp app/build/outputs/apk/appname.apk appname.apk
artifacts:
expire_in: 7 days
paths:
- appname.apk
Let me explain!
assembleDebug:
This represents Job that will be done in the build stage. We can provide any name which we want to provide here.
image: jangrewe/gitlab-ci-android
CI Job will run inside a Docker Image. Downloading the image, with everything installed, is a bit faster than running the SDK install process every time.
only:
- master
It specifies that the stage will be applied only when there is a push on a specific branch. Here, I applied to the master branch.
script:
- echo $KEYSTORE_FILE | base64 -d > my.keystore
- ./gradlew assembleRelease
-Pandroid.injected.signing.store.file=$(pwd)/my.keystore
-Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD
-Pandroid.injected.signing.key.alias=$KEY_ALIAS
-Pandroid.injected.signing.key.password=$KEY_PASSWORD
- cp app/build/outputs/apk/appname.apk appname.apk
This is the stage where we build and sign our application. We want a release build with an app that is signed with a release key store. We will use GitLab CI/CD variables here. We will store our secret keys and passwords inside the variables and they will be accessible only for our job runners. It’s easy to store the Keystore passwords and alias, but what are we going to do with the Keystore file? We will store it as a base64 string.
base64 -w 0 ~/.android/debug.keystore
Now that we have our base64 string, let’s put it inside CI/CD variables. In GitLab project, go to Settings -> CI / CD -> Environment variables (expand). Add your keys and passwords. Make sure that your variable names match the same in the script.

- KEY_ALIAS
- KEY_PASSWORD
- KEYSTORE_PASSWORD
Protected variables will be used only in protected branches.
Now that we have the keys, our release job will decode the base64 back to the Keystore file and will run gradlew assembleRelease
with the keys.
cp app/build/outputs/apk/appname.apk appname.apk
^^ This is your generated release APK path. Change it as per your build or product flavor requirements.
artifacts:
expire_in: 7 days
paths:
- appname.apk
The artifacts are passed to the next job (upload) and are also available for download from GitLab UI.
Here your Build Stage is completed.VOILA!! Now let us move to the deploy stage.
Firebase Distribution Deployment:
Here we will use,
image: node: latest
deploy_production:
image: node:latest
stage: deploy
only:
- master
script:
- npm install -g firebase-tools
- firebase appdistribution:distribute app.apk --app your_firebase_app_id --release-notes-file release-notes.txt --groups "test-group" --token "$FIREBASE_CI_TOKEN"
We need the firebase app id and firebase CI token here. Firebase app id can be found in the project setting of your firebase console. Firebase CI Token can be generated using the below guide.
https://firebase.google.com/docs/cli#cli-ci-systems
Add this token as a variable in GitLab as FIREBASE_CI_TOKEN.
Once code is merged in the master branch, the release build will generate and will upload directly on firebase app distribution!!
Yeah, it looks like magic!
.gitlab-ci.yml (complete file)
before_script:
- export GRADLE_USER_HOME=$(pwd)/.gradle
- chmod +x ./gradlew
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
stages:
- build
- deploy
assembleDebug:
image: jangrewe/gitlab-ci-android
stage: build
only:
- master
script:
- echo $KEYSTORE_FILE | base64 -d > my.keystore
- ./gradlew assembleRelease
-Pandroid.injected.signing.store.file=$(pwd)/my.keystore
-Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD
-Pandroid.injected.signing.key.alias=$KEY_ALIAS
-Pandroid.injected.signing.key.password=$KEY_PASSWORD
- cp app/build/outputs/apk/appname.apk appname.apk
artifacts:
expire_in: 7 days
paths:
- appname.apk
deploy_production:
image: node:latest
stage: deploy
only:
- master
script:
- npm install -g firebase-tools
- firebase appdistribution:distribute app.apk --app your_firebase_app_id --release-notes-file release-notes.txt --groups "test-group" --token "$FIREBASE_CI_TOKEN"
Useful Links:
GitLab CI-CD Configuration: https://docs.gitlab.com/ee/ci/yaml/
Firebase App Distribution CLI: https://firebase.google.com/docs/app-distribution/android/distribute-cli
Gradle build flavors: https://stackoverflow.com/questions/21307444/gradle-build-only-a-flavour
If you like SMASH the CLAP 👏 Button and DISAPPEAR it from Medium.