Skip to content

Automating Android App Releases with Jenkins, Gradle, and Vault

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:

  1. Setting up Google Play credentials.
  2. Checking out source code.
  3. Fetching signing credentials from Vault.
  4. Building a signed Android App Bundle (AAB).
  5. Requiring manual approval before release.
  6. 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

  1. Go to Google Cloud Console.
  2. Create a service account named something like jenkins-play-publisher.
  3. Assign the Service Account User role.
  4. 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

  1. Go to the “Keys” tab of the service account.
  2. Click “Add Key” → “Create new key” → JSON.
  3. Save the file securely (service-account.json).

Step 3: Add JSON Key to Jenkins

  1. In Jenkins, go to Manage Jenkins → Credentials.
  2. 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

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

Automating Android App Releases with Jenkins  Gradle and Vault

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()}"
                        }
                    }
                }
            }
        }
    }
}

Automating Android App Releases with Jenkins  Gradle and Vault

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.

Published inAutomationci/cdJenkinsLinuxScriptSecurity