CI/CD Setup for Mobile Applications via Jenkins
Jenkins is chosen when you need complete infrastructure control, already have on-premise Jenkins in company, or security policies prohibit cloud CI. For mobile development this means: Linux controller for Android, macOS agent for iOS.
Jenkins Architecture for Mobile Development
Jenkins Controller (Linux/macOS)
├── macOS Agent (for iOS)
│ ├── Xcode 16
│ ├── fastlane
│ └── CocoaPods / SPM
└── Linux Agent (for Android)
├── JDK 17
├── Android SDK
└── Gradle
Agents connect via SSH (Launch agents via SSH) or JNLP. For macOS agent—must be account with Keychain access to code signing certificates.
Jenkinsfile: Declarative Pipeline
pipeline {
agent none
environment {
FASTLANE_SKIP_UPDATE_CHECK = 'true'
MATCH_PASSWORD = credentials('match-passphrase')
FIREBASE_TOKEN = credentials('firebase-cli-token')
}
stages {
stage('Test iOS') {
agent { label 'macos-m2' }
steps {
checkout scm
sh 'bundle install --path vendor/bundle'
sh 'bundle exec fastlane test'
}
post {
always {
junit 'fastlane/test_output/report.junit'
}
}
}
stage('Build iOS Beta') {
agent { label 'macos-m2' }
when {
branch 'main'
}
steps {
sh 'bundle exec fastlane beta'
}
}
stage('Build Android') {
agent { label 'linux-android' }
steps {
checkout scm
sh './gradlew test assembleRelease'
archiveArtifacts artifacts: 'app/build/outputs/apk/release/*.apk'
}
}
}
post {
failure {
slackSend(
channel: '#mobile-ci',
color: 'danger',
message: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}
}
credentials('match-passphrase')—Jenkins Credentials Store. Secrets aren't in Jenkinsfile, only ID references.
Keychain Management on macOS Agent
Main pain point—Jenkins + iOS: running as background daemon, codesign cannot open Keychain without unlock. Solution—unlock at pipeline start:
stage('Unlock Keychain') {
agent { label 'macos-m2' }
environment {
KEYCHAIN_PASSWORD = credentials('macos-keychain-password')
}
steps {
sh 'security unlock-keychain -p $KEYCHAIN_PASSWORD ~/Library/Keychains/login.keychain-db'
sh 'security set-keychain-settings -lut 3600 ~/Library/Keychains/login.keychain-db'
}
}
set-keychain-settings -lut 3600—keychain doesn't lock for 1 hour (enough for build). Without this, after 5 minutes idle keychain locks automatically and codesign gets errSecInteractionNotAllowed.
Artifact Caching
Jenkins lacks built-in smart cache like GitHub Actions. Options:
Shared directory on agent. Folder ~/.gradle or ~/Library/Caches/CocoaPods saves between builds automatically—if always using same agent (sticky agent).
Jenkins Workspace Caching Plugin. Caches by hash of Podfile.lock/build.gradle, like GitHub Actions cache.
Artifactory/Nexus. For enterprise—cache Maven/Gradle dependencies through internal repository. GRADLE_USER_HOME=~/.gradle + Nexus mirror.
Parallel Stages
stage('Test Parallel') {
parallel {
stage('iOS Tests') {
agent { label 'macos-m2' }
steps { sh 'bundle exec fastlane test' }
}
stage('Android Tests') {
agent { label 'linux-android' }
steps { sh './gradlew test' }
}
}
}
Parallel iOS and Android test run reduces total time from ~20 to ~12 minutes.
Common Issues
- xcodebuild cannot find simulator—must explicitly boot with xcrun simctl boot "iPhone 16" before test run on new agent
- Gradle wrapper not found—need chmod +x gradlew in beginning of step
- Build number conflicts on parallel builds—use ${env.BUILD_NUMBER} as suffix
Timeline
Jenkins Pipeline setup with macOS + Linux agents, test + deploy lanes: 1–2 weeks (including agents). Parallel builds support, Slack/Jira integration, Artifactory setup: another week. Cost calculated individually.







