I know macOS isn’t the most common platform for running Jenkins in production, but it’s actually essential in one specific scenario: building and testing iOS or macOS applications, which require Apple’s toolchain (Xcode, xcodebuild, simulators) that simply doesn’t exist anywhere else. I’ve set up Jenkins on macOS a few times for exactly this reason, and I’ll walk through the entire process here — installation, configuration, and getting a working pipeline running.
Why Run Jenkins on macOS?
The most common reason is Apple platform development. If you’re building iOS, iPadOS, watchOS, or macOS apps, you need Xcode’s build tools, which are only officially available on macOS. Beyond that, some teams also just prefer macOS as a general development machine and want a local Jenkins instance for personal automation or a small team’s CI needs.
Jenkins Architecture on macOS
You can run Jenkins as a standalone Java process (fine for individual/local use) or, more robustly, using Docker or Homebrew’s service management. For CI purposes tied to Apple’s build tools, macOS typically serves as a Jenkins agent connected to a Linux-based controller — this way, you get the best of both: a lightweight Linux controller managing everything, dispatching only the Xcode-dependent jobs to the macOS agent.
Step 1: Install Prerequisites
Jenkins requires Java. Install it via Homebrew:
brew install openjdk@17
sudo ln -sfn /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk \
/Library/Java/JavaVirtualMachines/openjdk-17.jdk
java -version
If you don’t have Homebrew installed yet:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Step 2: Install Jenkins via Homebrew
brew install jenkins-lts
Start it as a background service:
brew services start jenkins-lts
By default, Jenkins runs on port 8080. Open http://localhost:8080 in your browser to access the setup wizard.
Step 3: Unlock Jenkins
Retrieve the initial admin password:
cat /opt/homebrew/var/jenkins_home/secrets/initialAdminPassword
(Note: on Intel Macs, the path is typically /usr/local/var/jenkins_home/secrets/initialAdminPassword.)
Paste this password into the setup wizard, then choose “Install suggested plugins” to get a good default plugin set.
Step 4: Create Your Admin User
Follow the setup wizard to create your first admin account, then confirm the Jenkins URL (typically http://localhost:8080/).
Step 5: Install Xcode and Command Line Tools
For any Apple platform build work, install Xcode from the Mac App Store, then install the command line tools:
xcode-select --install
sudo xcodebuild -license accept
Verify:
xcodebuild -version
Step 6: Install Additional Build Tools
Most iOS/macOS projects rely on additional tooling:
brew install cocoapods
brew install fastlane
sudo gem install bundler
Step 7: Connect a macOS Agent to a Jenkins Controller (Recommended Setup)
If you already have a Linux-based Jenkins controller, connect your Mac as an agent rather than running the controller itself on macOS:
- On the Jenkins controller, go to Manage Jenkins > Nodes > New Node.
- Name it (e.g.,
macos-build-agent), select “Permanent Agent.” - Set the remote root directory (e.g.,
/Users/jenkins/agent). - Set labels like
macos xcode. - Choose the launch method — typically “Launch agent by connecting it to the controller” (JNLP) or via SSH if the controller can reach the Mac directly.
On the Mac itself, download the agent JAR and connect:
curl -sO http://your-jenkins-controller:8080/jnlpJars/agent.jar
java -jar agent.jar -url http://your-jenkins-controller:8080/ \
-secret <secret-from-node-config> -name "macos-build-agent" \
-workDir "/Users/jenkins/agent"
I recommend setting this up to run persistently using launchd so the agent reconnects automatically after a reboot.
Step 8: Writing a Jenkinsfile for an iOS Build
Here’s a realistic pipeline targeting the macOS agent specifically:
pipeline {
agent { label 'macos && xcode' }
stages {
stage('Checkout') {
steps {
git branch: 'main', url: 'https://github.com/yourusername/your-ios-app.git'
}
}
stage('Install Dependencies') {
steps {
sh 'pod install'
}
}
stage('Run Tests') {
steps {
sh '''
xcodebuild test \
-workspace YourApp.xcworkspace \
-scheme YourApp \
-destination "platform=iOS Simulator,name=iPhone 15"
'''
}
}
stage('Build Archive') {
steps {
sh '''
xcodebuild archive \
-workspace YourApp.xcworkspace \
-scheme YourApp \
-archivePath build/YourApp.xcarchive
'''
}
}
stage('Export IPA') {
steps {
sh '''
xcodebuild -exportArchive \
-archivePath build/YourApp.xcarchive \
-exportPath build/ \
-exportOptionsPlist ExportOptions.plist
'''
archiveArtifacts artifacts: 'build/*.ipa', fingerprint: true
}
}
}
post {
always {
junit 'build/reports/*.xml'
}
}
}
Step 9: Code Signing Considerations
Code signing is often the trickiest part of iOS CI on macOS. Store your signing certificate and provisioning profile securely (typically imported into a dedicated CI keychain), and reference them using fastlane match or manual keychain setup in your pipeline:
security create-keychain -p "" ci-keychain.keychain
security import certificate.p12 -k ci-keychain.keychain -P "$CERT_PASSWORD" -T /usr/bin/codesign
security list-keychains -s ci-keychain.keychain
I’d strongly recommend fastlane match for managing this across a team, since manual keychain juggling gets error-prone fast.
Step 10: Running Jenkins as a Background Service on Reboot
Homebrew services handle this automatically:
brew services start jenkins-lts
To confirm it’s set to launch on startup:
brew services list
Real-World Scenario: Mixed Linux/macOS CI Setup
A pattern I’ve used successfully:
- A Linux-based Jenkins controller handles job scheduling, web UI, and non-Apple builds.
- A macOS Mac Mini (or Mac in the cloud, via a provider like MacStadium) connects as a dedicated agent labeled
macos. - Backend/web jobs run on Linux agents; iOS/macOS jobs are pinned to the macOS agent using
agent { label 'macos' }. - Fastlane handles code signing and App Store Connect uploads as the final pipeline stage.
Running Multiple Xcode Versions Side by Side
Different projects often require different Xcode versions. On macOS, multiple Xcode versions can coexist in /Applications (e.g., Xcode-14.app, Xcode-15.app), and you can switch the active version per build using xcode-select:
stage('Select Xcode Version') {
steps {
sh 'sudo xcode-select -s /Applications/Xcode-15.app'
sh 'xcodebuild -version'
}
}
Since xcode-select changes a system-wide setting, be cautious about concurrent builds on the same agent targeting different Xcode versions simultaneously — you may need to serialize those specific jobs or use separate agents per Xcode version to avoid conflicts.
Uploading Builds to TestFlight and the App Store
Once your pipeline produces a signed .ipa, the natural next step is distribution. Fastlane’s pilot and deliver actions handle this well from within a Jenkins pipeline:
stage('Upload to TestFlight') {
steps {
sh 'bundle exec fastlane pilot upload --ipa build/YourApp.ipa'
}
}
Store your App Store Connect API key as a Jenkins Secret File credential rather than embedding it directly in your Fastlane configuration.
Managing macOS Agent Capacity
Apple’s licensing terms require that macOS runs on Apple hardware, which limits your options for horizontal scaling compared to Linux, where you can spin up as many cloud VMs as you want. Plan your macOS agent capacity around your team’s actual build concurrency needs, and consider a mix of dedicated Mac Minis or Mac Studios alongside a cloud Mac provider for burst capacity during busy release periods.
Troubleshooting Common macOS Setup Issues
- “xcodebuild: error: unable to find a destination”: Verify the simulator name and iOS version match what’s actually installed via
xcrun simctl list devices. - Agent disconnects frequently: Check for macOS sleep settings interrupting the agent process; disable sleep on build machines or use
caffeinatewhen launching the agent. - Keychain access errors during code signing: Make sure the CI keychain is unlocked and added to the search list before the build step runs.
- Permission issues with Homebrew-installed Jenkins: Confirm the Jenkins service is running under the correct user account with access to Xcode and simulators.
Security Best Practices
- Store signing certificates and provisioning profiles as Jenkins Secret File credentials, never as plain files sitting on disk long-term.
- Restrict SSH/JNLP agent connections to trusted networks only.
- Regularly rotate any API tokens used for App Store Connect or TestFlight uploads.
- Keep macOS, Xcode, and Jenkins itself updated to avoid known vulnerabilities.
FAQs
Q: Can I run the entire Jenkins controller on macOS, not just an agent? Yes, technically you can, but it’s uncommon for production use since macOS licensing and hardware aren’t designed for typical server workloads. Most teams use macOS only as a specialized build agent.
Q: Do I need a physical Mac, or can I use a cloud Mac? Cloud Mac providers like MacStadium, AWS EC2 Mac instances, or GitHub-hosted macOS runners (if not using Jenkins directly) are all valid options if you don’t want to maintain physical hardware.
Q: How do I keep the Jenkins agent running after I log out of the Mac? Set it up as a launchd daemon rather than running it manually in a terminal session, so it persists independently of any logged-in user session.
Q: Can this macOS agent also build Android apps? Yes — since Android builds don’t require Xcode, you could use the same macOS agent for Android too, though it’s more common to route those to a Linux agent for cost and resource efficiency.
Summary
Setting up Jenkins on macOS is mainly about enabling Apple platform builds — Xcode, simulators, and code signing simply aren’t available anywhere else. Whether you run Jenkins standalone via Homebrew or, more commonly, connect a Mac as a dedicated agent to an existing Linux controller, the setup process is straightforward once you’ve got Java, Xcode, and the agent connection sorted out.
