Mobile Development 10 min read

Automate Android App Builds with Jenkins and Docker: A Step‑by‑Step Guide

Learn how to set up a complete Android CI/CD pipeline by installing JDK, Android SDK, and Gradle, creating Docker images for builds, configuring Jenkins on Kubernetes, and automating packaging and deployment, with detailed commands and troubleshooting tips for efficient mobile app releases.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Automate Android App Builds with Jenkins and Docker: A Step‑by‑Step Guide

Introduction

As automation becomes widespread, many companies automate application releases. While front‑end and back‑end projects are often automated, mobile projects are not. This guide outlines steps to automate Android builds, starting with manual testing and moving toward full automation.

Environment Setup

Install JDK

Download JDK from Oracle and extract it, then set JAVA_HOME and update PATH.

<code># tar xf jdk-8u291-linux-x64.tar.gz -C /usr/local/
# vim /etc/profile
export JAVA_HOME=/usr/local/jdk1.8.0_291
export PATH=$PATH:$JAVA_HOME/bin

# source /etc/profile
# java -version
java version "1.8.0_291"
...</code>

Install Android SDK

Download the command‑line tools from the Android developer site and configure ANDROID_HOME.

<code># unzip commandlinetools-linux-7302050_latest.zip -d /usr/local/
# vim /etc/profile
export ANDROID_HOME=/usr/local
export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/cmdline-tools/bin

# source /etc/profile
# sdkmanager --list
# sdkmanager "platforms;android-29"
</code>

If you encounter “Could not determine SDK root” errors, create a

latest

directory under

cmdline-tools

and move

bin

,

lib

, and

source.properties

into it.

<code>latest/
├── bin
├── lib
├── NOTICE.txt
└── source.properties
</code>

Then update the environment variables accordingly.

<code># vim /etc/profile
export ANDROID_HOME=/usr/local
export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/cmdline-tools/latest/bin
</code>

Install Gradle

Download Gradle and unpack it, then set GRADLE_HOME and update PATH.

<code># unzip gradle-7.1.1-bin.zip -d /usr/local/
# vim /etc/profile
export GRADLE_HOME=/usr/local/gradle-7.1.1
export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/cmdline-tools/latest/bin:$GRADLE_HOME/bin

# source /etc/profile
# gradle -v
Welcome to Gradle 7.1.1!
...
</code>

Manual Build Test

Clone Repository

<code># git clone http://192.168.100.180/app/android/newcrm1.0.git
</code>

Build and Package

<code># gradle build & gradle assemble
# curl -F "file=@/tmp/example.ipa" -F "uKey=aa18132c4d9afedfa9cd2c054213c867" -F "_api_key=bb66fdd1c5a4c247b016e0ab88a54fdd" https://upload.pgyer.com/apiv1/app/upload
</code>

Automated Build with Jenkins

Jenkins runs in Kubernetes; Android builds can be executed on a Jenkins‑slave container.

Workflow diagram:

Create Docker Image for Build

Build a Docker image that contains the Android SDK and Gradle.

<code>FROM gradle:5.6.4-jdk8
ADD cmdline-tools.tar.gz /usr/local
ENV ANDROID_HOME /usr/local
ENV PATH ${ANDROID_HOME}/cmdline-tools/latest/bin:${PATH}
</code>

Build the image:

<code>docker build -t registry.cn-hangzhou.aliyuncs.com/rookieops/android-sdk:v1 .
</code>

Install additional SDK components inside a running container, then commit the container as a new image.

<code>docker exec -it --rm registry.cn-hangzhou.aliyuncs.com/rookieops/android-sdk:v1 bash
# sdkmanager "platforms;android-29" "platform-tools" "build-tools;29.0.2"

# docker commit <container_id> registry.cn-hangzhou.aliyuncs.com/rookieops/android-sdk:v2
# docker push registry.cn-hangzhou.aliyuncs.com/rookieops/android-sdk:v2
</code>

Jenkinsfile

The Jenkinsfile defines the pipeline, pulls the custom image, builds the APK, and uploads it.

<code>#!groovy

@Library('lotbrick') _

def dingmes = new org.devops.sendDingTalk()

pipeline {
    agent {
        kubernetes {
            label "jenkins-slave-${UUID.randomUUID().toString()}"
            yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: gradle
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/android-sdk:v2
    command: ['cat']
    tty: true
    volumeMounts:
      - name: caches
        mountPath: /home/gradle/.gradle/caches/
  volumes:
    - name: caches
      hostPath:
        path: "/data/jenkins-job/${JOB_NAME}/gradle/"
"""
        }
    }
    environment {
        GIT_CREDENTIAL_ID = 'git-token'
        UKEY = "xxxx"
        API_KEY = "xxx"
        UPLOAD_URL = "https://upload.pgyer.com/apiv1/app/upload"
        DINGTALKHOOK = "https://oapi.dingtalk.com/robot/send?access_token=..."
    }
    triggers {
        GenericTrigger(
            genericVariables: [
                [key: 'ref', value: '$.ref'],
                [key: 'before', value: '$.before'],
                [key: 'after', value: '$.after'],
                [key: 'hook_name', value: '$.hook_name']
            ],
            causeString: 'Triggered on $ref',
            token: env.JOB_NAME,
            printContributedVariables: true,
            printPostContent: true,
            regexpFilterText: '$ref',
            regexpFilterExpression: 'refs/heads/(pre|master)'
        )
    }
    options {
        timeout(time: 25, unit: 'MINUTES')
    }
    stages {
        stage('Checkout SCM') {
            steps {
                checkout(scm)
            }
        }
        stage('Build & Push') {
            steps {
                container('gradle') {
                    script {
                        sh """
gradle build & gradle assemble
ls app/build/outputs/apk/release/*.apk
"""
                        PACKAGE_DIR = sh(script: "ls app/build/outputs/apk/release/*.apk", returnStdout: true).trim()
                        FULL_DIR = "${WORKSPACE}/${PACKAGE_DIR}"
                        sh """
curl -F \"file=@${FULL_DIR}\" -F \"uKey=${UKEY}\" -F \"_api_key=${API_KEY}\" ${UPLOAD_URL}
"""
                    }
                }
            }
        }
    }
    post {
        success {
            script {
                println "success: only runs on build success"
                dingmes.SendDingTalk("Build succeeded ✅")
            }
        }
        failure {
            script {
                println "failure: only runs on build failure"
                dingmes.SendDingTalk("Build failed ❌")
            }
        }
    }
}
</code>

Configure Pipeline in Kubesphere

Add the project, set the repository branch and path, and save the configuration. After a successful build, DingTalk receives a notification and the DevOps console shows detailed build information.

Conclusion

The guide demonstrates how to use Jenkins to publish an Android project. The process is straightforward, though the Docker image that includes the Android SDK can be large (around 1 GB), making it the biggest image built so far.

DockerCI/CDAndroidGradleMobile AutomationJenkins
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.