This guide walks you through building a Jenkins CI/CD pipeline to build and publish a signed Android App Bundle (AAB) to the Google Play Store. It includes secure signing credentials stored in HashiCorp Vault, manual approval for releases, and multilingual Play Store publishing – all automated with Jenkins and Gradle.
Overview of the Pipeline
The pipeline covers:
- Setting up Google Play credentials.
- Checking out source code.
- Fetching signing credentials from Vault.
- Building a signed Android App Bundle (AAB).
- Requiring manual approval before release.
- Publishing to Google Play with multilingual release notes.
1. Setting Up Google Play Credentials in Jenkins
Before publishing to Google Play, you must authenticate Jenkins using a service account from Google Cloud.
Note: You must install the Google Play Android Publisher plugin in Jenkins before continuing. This plugin enables Jenkins to upload Android App Bundles (AAB) to the Google Play Store.
Step 1: Create a Service Account
- Go to Google Cloud Console.
- Create a service account named something like
jenkins-play-publisher
. - Assign the Service Account User role.
- In the Google Play Console, go to
Settings → API Access
, link the Google Cloud project, and grant access to the service account.
Step 2: Download the JSON Key
- Go to the “Keys” tab of the service account.
- Click “Add Key” → “Create new key” → JSON.
- Save the file securely (
service-account.json
).
Step 3: Add JSON Key to Jenkins
- In Jenkins, go to
Manage Jenkins → Credentials
. - Under a suitable scope (e.g., Global), click Add Credentials:
- Kind:
Secret file
- File: Upload the downloaded
service-account.json
- ID:
google-play-service-account
- Description:
Google Play Service Account
- Kind:
This key will be referenced in your Jenkins pipeline:
androidApkUpload googleCredentialsId: 'google-play-service-account', ...
(Optional) .p12
Key for Legacy Tools
If required (older plugins):
openssl pkcs12 -export -inkey private-key.pem -in service-account.pem -out service-account.p12
Modern plugins use JSON, which is preferred.
2. Checking Out Source Code
This step uses Jenkins’ checkout scm
to retrieve the source code from your Git repository.
stage('Checkout') {
steps {
checkout scm
}
}
3. Fetching Signing Credentials from Vault
We fetch the Android keystore and credentials from HashiCorp Vault securely:
stage('Fetch Signing Credentials') {
steps {
script {
def response = sh(script: "curl -sS -H 'X-Vault-Token: $VAULT_TOKEN' $VAULT_ADDR/${env.SIGNING_KEYSTORE_PATH}", returnStdout: true).trim()
def jsonData = readJSON text: response
writeFile file: "${WORKSPACE}/release-keystore.jks", text: jsonData.data.data["release-keystore.jks"]
env.SIGNING_KEY_ALIAS = jsonData.data.data.keyAlias
env.SIGNING_KEY_PASSWORD = jsonData.data.data.keyPassword
env.SIGNING_STORE_PASSWORD = jsonData.data.data.storePassword
}
}
}
4. Building a Signed AAB
Gradle will build the release AAB using signing properties:
stage('Build AAB') {
steps {
script {
sh """
./gradlew bundleProdRelease \
-PbuildNumber=${params.VERSION_CODE} \
-PversionName=${params.VERSION_NAME} \
-PstoreFile='${WORKSPACE}/release-keystore.jks' \
-PkeyAlias='${env.SIGNING_KEY_ALIAS}' \
-PkeyPassword='${env.SIGNING_KEY_PASSWORD}' \
-PstorePassword='${env.SIGNING_STORE_PASSWORD}'
"""
}
}
}
5. Manual Approval Before Release
Before deploying to Google Play, request manual approval via Jenkins UI:
stage('Approval') {
steps {
script {
timeout(time: 1, unit: 'HOURS') {
input(
message: "Release to Google Play? Version Code: ${params.VERSION_CODE}",
submitter: 'admin,developer',
ok: 'Proceed'
)
}
}
}
}
6. Publishing to Google Play
The Jenkins Google Play plugin will upload the AAB using the credentials added earlier:
stage('Publish to Google Play') {
steps {
script {
androidApkUpload googleCredentialsId: 'google-play-service-account',
filesPattern: '**/build/outputs/bundle/prodRelease/*.aab',
trackName: params.RELEASE_TRACK,
rolloutPercentage: params.ROLLOUT_PERCENTAGE.toInteger().toString(),
releaseName: params.VERSION_NAME,
recentChangeList: [
[language: 'en-US', text: params.RELEASE_NOTES_EN]
]
}
}
}
7. Gradle Configuration for Signing
Here’s the signing config in your build.gradle
:
signingConfigs {
release {
storeFile file(project.property('storeFile'))
storePassword project.property('storePassword')
keyAlias project.property('keyAlias')
keyPassword project.property('keyPassword')
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
Full Jenkinsfile
After you start a pipeline with “Build with Parameters” you will need to set VERSION_CODE, VERSION_NAME, RELEASE_TRACK, ROLLOUT_PERCENTAGE and RELEASE_NOTES

pipeline {
agent any
environment {
ANDROID_HOME = '/usr/lib/android-sdk/'
SIGNING_KEYSTORE_PATH = 'v1/google-signing-key/data/key'
}
options {
timestamps()
skipDefaultCheckout(true)
disableConcurrentBuilds()
}
parameters {
string(name: 'VERSION_CODE', defaultValue: '1')
string(name: 'VERSION_NAME', defaultValue: '1.0.0')
choice(name: 'RELEASE_TRACK', choices: ['internal', 'alpha', 'beta', 'production'])
string(name: 'ROLLOUT_PERCENTAGE', defaultValue: '100')
text(name: 'RELEASE_NOTES_EN', defaultValue: 'Bug fixes and improvements.')
}
stages {
stage('Checkout') {
steps {
container('gradle') {
checkout scm
}
}
}
stage('Fetch Signing Credentials') {
steps {
container('gradle') {
script {
println("Fetching signing credentials from Vault")
def printWithNoTrace = { cmd ->
return sh(script: "#!/bin/sh -e\n${cmd}", returnStdout: true).trim()
}
def response = printWithNoTrace("curl -sS -H 'X-Vault-Token: $VAULT_TOKEN' $VAULT_ADDR/${env.SIGNING_KEYSTORE_PATH}")
def jsonData = readJSON text: response
writeFile file: "${WORKSPACE}/release-keystore.jks", text: jsonData.data.data["release-keystore.jks"]
env.SIGNING_KEY_ALIAS = jsonData.data.data.keyAlias
env.SIGNING_KEY_PASSWORD = jsonData.data.data.keyPassword
env.SIGNING_STORE_PASSWORD = jsonData.data.data.storePassword
println("Signing credentials fetched successfully")
}
}
}
}
stage('Build AAB') {
steps {
container('gradle') {
script {
println("Building AAB")
println("ANDROID_HOME: ${env.ANDROID_HOME}")
try {
withEnv(["ANDROID_HOME=${env.ANDROID_HOME}"]) {
sh(script: """
#!/bin/bash
set +x
./gradlew bundleProdRelease -PbuildNumber=${params.VERSION_CODE} -PversionName=${params.VERSION_NAME} -PstoreFile='${WORKSPACE}/release-keystore.jks' -PkeyAlias='${env.SIGNING_KEY_ALIAS}' -PkeyPassword='${env.SIGNING_KEY_PASSWORD}' -PstorePassword='${env.SIGNING_STORE_PASSWORD}'
""")
}
} catch (Exception e) {
error "Building AAB failed: ${e.getMessage()}"
}
}
}
}
}
stage('Approval') {
steps {
script {
timeout(time: 1, unit: 'HOURS') {
input(
message: "Release to Google Play? Version Code: ${params.VERSION_CODE}",
submitter: 'admin,developer',
ok: 'Proceed'
)
}
}
}
}
stage('Publish to Google Play') {
steps {
container('gradle') {
script {
println("Publishing to Google Play")
try {
androidApkUpload googleCredentialsId: 'google-play-service-account',
filesPattern: '**/build/outputs/bundle/prodRelease/*.aab',
trackName: params.RELEASE_TRACK,
rolloutPercentage: params.ROLLOUT_PERCENTAGE.toInteger().toString(),
releaseName: params.VERSION_NAME,
recentChangeList: [
[language: 'en-US', text: params.RELEASE_NOTES_EN]
]
} catch (Exception e) {
error "Publishing to Google Play failed: ${e.getMessage()}"
}
}
}
}
}
}
}

This Jenkins pipeline builds, signs, and deploys Android AABs securely to Google Play, with approval gating, multilingual notes, and Vault-based key storage. It’s a robust, production-ready solution that simplifies your mobile release lifecycle.