Langsung ke konten

Jenkins Mac Build Agent: Panduan Pengaturan Lengkap

Konfigurasi agen build Jenkins pada Mac Mini M4 khusus. Otomatiskan build Xcode, jalankan pengujian iOS, dan deploy ke TestFlight dengan kontrol penuh atas infrastruktur build Anda.

CI/CD Baca 35 menit Diperbarui Oktober 2026

Mengapa Mac Khusus untuk Jenkins?

Jenkins adalah platform CI/CD yang paling banyak digunakan, menjalankan lebih dari 50% pipeline build enterprise. Namun, membangun aplikasi iOS dengan Jenkins memerlukan macOS, dan macOS secara legal hanya dapat berjalan di perangkat keras Apple. Mac Mini M4 khusus memberi Anda:

  • Lingkungan Build yang Konsisten

    Perangkat keras sama, OS sama, alat sama setiap build. Tidak ada build yang tidak stabil akibat pergeseran lingkungan.

  • Akses Root Penuh

    Instal alat apa pun, konfigurasikan pengaturan sistem, kelola keychain untuk penandatanganan kode.

  • Performa Apple Silicon

    Chip M4 memberikan build Xcode 2-3x lebih cepat dibandingkan Mac Mini Intel.

  • Cache Persisten

    Cache DerivedData, CocoaPods, dan SPM tetap ada di antara setiap build.

Prasyarat

  • Sebuah server build Mac mini M4 khusus dari My Remote Mac (mulai $85/bln, USD, belum termasuk pajak)
  • Sebuah Jenkins controller (dapat berada di Linux, Docker, atau VM cloud mana pun)
  • Akses SSH ke Mac Mini Anda
  • Xcode terinstal di Mac Mini (lihat panduan GitHub Actions kami untuk langkah-langkah instalasi Xcode)

Langkah 1: Instal Java JDK di macOS

Agen Jenkins memerlukan Java Runtime Environment. Rilis Jenkins saat ini mendukung Java 21 dan Java 25 (Java 17 tidak lagi didukung), jadi kami menginstal JDK 21 (LTS) melalui Homebrew untuk kemudahan pengelolaan.

# Install Homebrew if not already present
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

# Install Java JDK 21 (LTS): current Jenkins releases support Java 21 and 25
brew install openjdk@21

# Create the symlink for system Java wrappers
sudo ln -sfn /opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk \
  /Library/Java/JavaVirtualMachines/openjdk-21.jdk

# Add to PATH
echo 'export PATH="/opt/homebrew/opt/openjdk@21/bin:$PATH"' >> ~/.zprofile
source ~/.zprofile

# Verify Java installation (should report version 21)
java -version

# Tools used by the pipeline in Step 3 (CocoaPods only if you use a Podfile)
brew install xcbeautify cocoapods

Langkah 2: Konfigurasikan Agen Jenkins

Ada dua metode utama untuk menghubungkan agen Jenkins: agen inbound (agen terhubung ke controller; dulu disebut JNLP) dan SSH (controller terhubung ke agen). Kami membahas keduanya.

Metode A: Agen SSH (Direkomendasikan)

Ini adalah pendekatan yang paling andal. Jenkins terhubung ke Mac melalui SSH dan mengelola proses agen dari jarak jauh.

# On the Mac mini: create a dedicated, non-admin Jenkins user
sudo sysadminctl -addUser jenkins -fullName "Jenkins Agent" -password "STRONG_PASSWORD_HERE"

# Create its home folder from the macOS template (includes ~/Library)
sudo createhomedir -c -u jenkins

# If Remote Login is limited to some users (System Settings > General >
# Sharing > Remote Login), allow the jenkins user to connect over SSH
sudo dseditgroup -o edit -a jenkins -t user com.apple.access_ssh

# Create SSH directory and add the Jenkins controller's public key
sudo mkdir -p /Users/jenkins/.ssh
sudo sh -c 'echo "ssh-rsa YOUR_JENKINS_CONTROLLER_PUBLIC_KEY" > /Users/jenkins/.ssh/authorized_keys'
sudo chmod 700 /Users/jenkins/.ssh
sudo chmod 600 /Users/jenkins/.ssh/authorized_keys
sudo chown -R jenkins:staff /Users/jenkins/.ssh

# Create a workspace directory
sudo mkdir -p /Users/jenkins/workspace
sudo chown jenkins:staff /Users/jenkins/workspace

Sekarang konfigurasikan agen di Jenkins:

  1. Buka Manage Jenkins > Nodes > New Node
  2. Atur namanya menjadi mac-mini-m4
  3. Atur Remote root directory menjadi /Users/jenkins/workspace
  4. Label: mac macos apple-silicon m4 ios
  5. Metode peluncuran: Launch agents via SSH
  6. Host: Alamat IP Mac Mini Anda
  7. Credentials: Tambahkan kunci privat SSH yang cocok dengan authorized_keys di Mac
  8. Host Key Verification Strategy: Known hosts file Verification Strategy. Sebelum peluncuran pertama, sambungkan sekali dari controller sebagai pengguna yang menjalankan Jenkins (ssh jenkins@YOUR_MAC_IP) untuk menambahkan Mac ke ~/.ssh/known_hosts miliknya

Metode B: Agen Inbound (WebSocket)

Gunakan metode ini ketika Jenkins controller tidak dapat menjangkau Mac secara langsung (mis., Mac berada di belakang firewall). Agen yang memulai koneksi. Dengan opsi -webSocket, agen hanya memakai port web controller (HTTP atau HTTPS), tanpa port TCP agen terpisah.

# First, create the node in Jenkins UI:
# Manage Jenkins > Nodes > New Node
# Launch method: "Launch agent by connecting it to the controller"
# Note the secret token from the node configuration page

# On the Mac mini, download the agent JAR as the jenkins user
# (download it again after each Jenkins controller upgrade)
sudo -u jenkins mkdir -p /Users/jenkins/jenkins-agent
sudo -u jenkins curl -fsSL -o /Users/jenkins/jenkins-agent/agent.jar \
  https://your-jenkins-url/jnlpJars/agent.jar

# Test the agent connection. -webSocket goes through the controller's
# HTTP(S) port: no separate TCP agent port to open
sudo -u jenkins /opt/homebrew/opt/openjdk@21/bin/java \
  -jar /Users/jenkins/jenkins-agent/agent.jar \
  -url https://your-jenkins-url \
  -secret YOUR_AGENT_SECRET \
  -name "mac-mini-m4" \
  -webSocket \
  -workDir "/Users/jenkins/workspace"

Jalankan Agen Inbound sebagai Layanan launchd

Agen berjalan sebagai LaunchAgent milik pengguna jenkins, jadi agen mulai saat pengguna itu login, bukan saat boot. Di Mac headless, aktifkan Automatic login untuk pengguna jenkins (System Settings > Users & Groups; tidak tersedia selama FileVault aktif) agar agen kembali online setelah reboot. Sesi yang sudah login ini juga memberi simulator, UI test, dan login keychain lingkungan pengguna yang mereka butuhkan.

# Create the LaunchAgent in the jenkins user's own Library
sudo -u jenkins mkdir -p /Users/jenkins/Library/LaunchAgents
sudo -u jenkins tee /Users/jenkins/Library/LaunchAgents/io.jenkins.agent.plist > /dev/null <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>io.jenkins.agent</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/opt/openjdk@21/bin/java</string>
        <string>-jar</string>
        <string>/Users/jenkins/jenkins-agent/agent.jar</string>
        <string>-url</string>
        <string>https://your-jenkins-url</string>
        <string>-secret</string>
        <string>YOUR_AGENT_SECRET</string>
        <string>-name</string>
        <string>mac-mini-m4</string>
        <string>-webSocket</string>
        <string>-workDir</string>
        <string>/Users/jenkins/workspace</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/jenkins/jenkins-agent/stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/jenkins/jenkins-agent/stderr.log</string>
</dict>
</plist>
EOF

# The agent loads at every login of the jenkins user. On a headless Mac,
# connect over VNC as an admin, open System Settings > Users & Groups and set
# "Automatically log in as" to jenkins (unavailable while FileVault is on),
# then restart:
sudo shutdown -r now

# After the restart, verify it's running
sudo launchctl print gui/$(id -u jenkins)/io.jenkins.agent | grep state

Langkah 3: Buat Pipeline Build iOS

Buat sebuah Jenkinsfile di root repositori Anda. Pipeline deklaratif ini membangun, menguji, dan secara opsional mengarsipkan aplikasi iOS Anda.

pipeline {
    agent { label 'mac && m4' }

    environment {
        // Pick the Xcode for every stage without sudo (path from: xcodes installed)
        DEVELOPER_DIR = '/Applications/Xcode-27.0.0.app/Contents/Developer'
        SCHEME        = 'MyApp'
        // Not WORKSPACE: Jenkins already uses it for the job's workspace path
        XC_WORKSPACE  = 'MyApp.xcworkspace'
        // Any device listed by: xcrun simctl list devices available
        DESTINATION   = 'platform=iOS Simulator,name=iPhone 17,OS=latest'
        // Relative to the job workspace, kept between builds
        DERIVED_DATA  = 'DerivedData'
    }

    options {
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Install Dependencies') {
            steps {
                sh '''
                    # Homebrew tools are not on the PATH of a non-interactive SSH session
                    export PATH="/opt/homebrew/bin:$PATH"

                    # Xcode comes from DEVELOPER_DIR: no sudo xcode-select in a pipeline
                    xcodebuild -version

                    # Install CocoaPods dependencies
                    if [ -f "Podfile" ]; then
                        pod install --repo-update
                    fi
                '''
            }
        }

        stage('Build') {
            steps {
                sh '''
                    # Jenkins runs sh -xe: without pipefail, xcbeautify's exit status would hide a failed build
                    set -o pipefail
                    export PATH="/opt/homebrew/bin:$PATH"
                    xcodebuild build \
                        -workspace "${XC_WORKSPACE}" \
                        -scheme "${SCHEME}" \
                        -destination "${DESTINATION}" \
                        -derivedDataPath "${DERIVED_DATA}" \
                        CODE_SIGNING_ALLOWED=NO \
                        | xcbeautify
                '''
            }
        }

        stage('Test') {
            steps {
                sh '''
                    set -o pipefail
                    export PATH="/opt/homebrew/bin:$PATH"
                    rm -rf TestResults.xcresult
                    xcodebuild test \
                        -workspace "${XC_WORKSPACE}" \
                        -scheme "${SCHEME}" \
                        -destination "${DESTINATION}" \
                        -derivedDataPath "${DERIVED_DATA}" \
                        -resultBundlePath "TestResults.xcresult" \
                        -parallel-testing-enabled YES \
                        | xcbeautify --report junit --report-path build/reports
                '''
            }
            post {
                always {
                    junit allowEmptyResults: true, testResults: 'build/reports/junit.xml'
                    archiveArtifacts artifacts: 'TestResults.xcresult/**', allowEmptyArchive: true
                }
            }
        }

        stage('Archive') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    set -o pipefail
                    export PATH="/opt/homebrew/bin:$PATH"
                    # Unsigned archive: checks that the Release build compiles.
                    # Fastlane signs and uploads the real build in Step 4.
                    xcodebuild archive \
                        -workspace "${XC_WORKSPACE}" \
                        -scheme "${SCHEME}" \
                        -archivePath "${DERIVED_DATA}/MyApp.xcarchive" \
                        -destination "generic/platform=iOS" \
                        CODE_SIGNING_ALLOWED=NO \
                        | xcbeautify
                '''
            }
        }
    }

    post {
        success {
            echo 'Build and tests passed!'
        }
        failure {
            echo 'Build or tests failed.'
        }
        cleanup {
            sh 'xcrun simctl shutdown all 2>/dev/null || true'
        }
    }
}

Langkah 4: Integrasikan Fastlane untuk Deployment

Fastlane menyederhanakan penandatanganan kode dan deployment TestFlight. Instal sekali di Mac:

# Install Fastlane on the Mac mini (one-time setup)
brew install fastlane

Tambahkan stage deploy ke Jenkinsfile Anda:

        stage('Deploy to TestFlight') {
            when {
                branch 'main'
            }
            environment {
                APP_STORE_CONNECT_API_KEY_ID     = credentials('app-store-key-id')
                APP_STORE_CONNECT_API_ISSUER_ID  = credentials('app-store-issuer-id')
                APP_STORE_CONNECT_API_KEY_CONTENT = credentials('app-store-key-content')
                MATCH_PASSWORD                   = credentials('match-password')
            }
            steps {
                sh '''
                    export PATH="/opt/homebrew/bin:$PATH"
                    fastlane beta
                '''
            }
        }

Dan buat fastlane/Fastfile yang sesuai:

default_platform(:ios)

platform :ios do
  desc "Push a new beta build to TestFlight"
  lane :beta do
    setup_ci

    # Fetch code signing certificates via match
    match(type: "appstore", readonly: true)

    # Increment build number
    increment_build_number(
      build_number: ENV["BUILD_NUMBER"]
    )

    # Build the app
    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      export_method: "app-store",
      derived_data_path: "DerivedData"
    )

    # Upload to TestFlight
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
      api_key: app_store_connect_api_key(
        key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
        issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"],
        key_content: ENV["APP_STORE_CONNECT_API_KEY_CONTENT"]
      )
    )
  end
end

Praktik Keamanan Terbaik

  • Gunakan pengguna Jenkins khusus

    Jangan pernah menjalankan agen Jenkins sebagai root. Buat pengguna khusus dengan izin minimal.

  • Simpan secret di Jenkins Credentials

    Jangan pernah menuliskan sertifikat, kunci API, atau kata sandi secara hardcode di Jenkinsfile. Gunakan plugin Jenkins Credentials.

  • Aktifkan hanya autentikasi berbasis kunci SSH

    Nonaktifkan autentikasi kata sandi untuk SSH. Gunakan kunci Ed25519 atau RSA 4096-bit.

  • Gunakan Firewall macOS

    Batasi koneksi masuk hanya ke IP Jenkins controller. My Remote Mac menyediakan firewall terkelola dengan akses API.

  • Selalu perbarui macOS dan Xcode

    Terapkan patch keamanan segera. Gunakan softwareupdate -l untuk memeriksa pembaruan.

FAQ

Bisakah saya menjalankan Jenkins controller di Mac yang sama?
Ya, tetapi kami merekomendasikan untuk memisahkannya. Jalankan controller di VM Linux atau container Docker, dan gunakan Mac secara eksklusif sebagai agen build. Ini menghindari perebutan sumber daya selama build berat.
Berapa banyak build bersamaan yang dapat ditangani Mac Mini M4?
Mac Mini M4 dengan RAM 16GB dapat dengan nyaman menangani 2 build Xcode bersamaan. Dengan 24GB, Anda dapat mendorong hingga 3. M4 Pro dengan 48GB menangani 4+ build bersamaan dengan mudah. Konfigurasikan "Number of executors" di pengaturan node Jenkins Anda sesuai kebutuhan.
Apakah saya memerlukan sesi GUI untuk simulator iOS?
Tidak. Simulator iOS bekerja dalam mode headless melalui SSH. Namun, jika Anda memerlukan GUI (mis., untuk tangkapan layar pengujian UI), gunakan VNC untuk masuk ke Mac dan pastikan sesi GUI aktif.
Bagaimana cara menangani penandatanganan kode di server headless?
Gunakan Fastlane Match untuk menyinkronkan sertifikat dari repo Git atau penyimpanan cloud. Sebagai alternatif, impor sertifikat ke keychain macOS dan buka kuncinya di pipeline Anda menggunakan security unlock-keychain.

Siap Mendongkrak Build Jenkins Anda?

Dapatkan Mac Mini M4 khusus untuk pipeline Jenkins CI/CD Anda. Mulai dari $85/bulan.

Harga My Remote Mac dalam USD, belum termasuk pajak.

Butuh detail lebih lanjut?

Jelajahi dokumentasi lengkap untuk panduan langkah demi langkah, referensi konfigurasi, dan pemecahan masalah.

Buka dokumentasi →