Mengapa GitLab Runner Self-Hosted di Mac?
GitLab menyediakan shared runner di Linux, tetapi membangun aplikasi iOS memerlukan macOS yang berjalan di perangkat keras Apple. Shared runner macOS milik GitLab sendiri terbatas dan mahal. Runner Mac Mini M4 self-hosted memberi Anda:
-
Menit CI/CD Tanpa Batas
Paket gratis GitLab mencakup 400 menit CI/CD pada shared runner. Pada self-hosted, tidak ada batas.
-
Apple Silicon Native
Bangun pada chip M4 yang sama dengan yang dijalankan perangkat pengguna Anda. Tanpa overhead terjemahan Rosetta.
-
Kontrol Penuh atas Lingkungan
Instal versi Xcode, simulator, alat, dan dependensi apa pun yang Anda butuhkan.
-
Cache Persisten
Cache DerivedData, paket SPM, dan CocoaPods tetap ada di antara setiap eksekusi pipeline.
Instal GitLab Runner
SSH ke server build Mac mini M4 khusus Anda dan instal GitLab Runner, beserta xcbeautify untuk log build yang mudah dibaca, menggunakan Homebrew:
# Connect to your Mac Mini M4
ssh admin@your-server-ip
# Install Homebrew (if not already installed)
/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 GitLab Runner, plus xcbeautify for the pipeline's build logs
brew install gitlab-runner xcbeautify
# Verify installation (prints the version and OS/Arch: darwin/arm64)
gitlab-runner --version
Instal sebagai Layanan macOS
# Run the runner as a launchd service. It is a LaunchAgent: it starts when
# your user session starts, so on a headless Mac enable automatic login for
# this user (System Settings > Users & Groups) to bring it back after a reboot
brew services start gitlab-runner
# Verify the service is running
brew services list | grep gitlab-runner
# gitlab-runner started admin ~/Library/LaunchAgents/homebrew.mxcl.gitlab-runner.plist
Daftarkan Runner
Buka proyek (atau grup) GitLab Anda dan navigasikan ke Settings > CI/CD > Runners > New project runner. Di Tags, masukkan macos dan m4 (pipeline di bawah menargetkan tag ini), buat runner, lalu salin token autentikasinya. Dengan token autentikasi, tag dan opsi runner lainnya diatur di sini, di GitLab, bukan di perintah register.
Daftarkan dengan Alur Registrasi Runner Baru (GitLab 16+)
# Register the runner using the authentication token from GitLab UI
# (GitLab 16+ uses authentication tokens instead of registration tokens).
# Tags, untagged jobs and locking come from the runner you created in GitLab:
# do not pass --tag-list or --run-untagged with an authentication token
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--token "YOUR_RUNNER_AUTHENTICATION_TOKEN" \
--executor "shell" \
--description "mac-mini-m4-runner"
# Confirm the runner can reach GitLab
gitlab-runner verify
Daftarkan dengan Token Registrasi Lama (GitLab 15 dan sebelumnya)
# For older GitLab instances using registration tokens
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "YOUR_REGISTRATION_TOKEN" \
--executor "shell" \
--description "mac-mini-m4-runner" \
--tag-list "macos,apple-silicon,m4,ios,xcode" \
--run-untagged="false"
Verifikasi Konfigurasi Runner
# View the runner config file
cat ~/.gitlab-runner/config.toml
# Expected output:
# concurrent = 2
# check_interval = 0
#
# [session_server]
# session_timeout = 1800
#
# [[runners]]
# name = "mac-mini-m4-runner"
# url = "https://gitlab.com/"
# token = "..."
# executor = "shell"
# [runners.cache]
# MaxUploadedArchiveSize = 0
# Adjust concurrency based on your hardware:
# Mac Mini M4 (16GB): concurrent = 2
# Mac Mini M4 Pro (24GB): concurrent = 3
# Mac Mini M4 Pro (48GB): concurrent = 4
Edit ~/.gitlab-runner/config.toml untuk menyesuaikan konkurensi:
# Edit the config
nano ~/.gitlab-runner/config.toml
# Set concurrent to match your hardware capacity
concurrent = 2
# Restart the service to apply changes
brew services restart gitlab-runner
Runner sekarang seharusnya muncul sebagai Online di proyek GitLab Anda pada Settings > CI/CD > Runners.
Buat .gitlab-ci.yml untuk iOS
Buat file .gitlab-ci.yml di root repositori Anda. Pipeline lengkap ini membangun, menguji, dan men-deploy aplikasi iOS Anda:
# .gitlab-ci.yml
stages:
- build
- test
- deploy
variables:
SCHEME: "MyApp"
WORKSPACE: "MyApp.xcworkspace"
# Any device listed by: xcrun simctl list devices available
DESTINATION: "platform=iOS Simulator,name=iPhone 17,OS=latest"
# Pick the Xcode for every job without sudo (path from: xcodes installed)
DEVELOPER_DIR: "/Applications/Xcode-27.0.0.app/Contents/Developer"
# Only run on our Mac runner
default:
tags:
- macos
- m4
before_script:
# The runner cleans the checkout (git clean -ffdx) before each job, so keep
# DerivedData outside it: one warm folder per project and concurrent job slot
- export DERIVED_DATA="${CI_BUILDS_DIR}/DerivedData/${CI_PROJECT_PATH_SLUG}-${CI_CONCURRENT_PROJECT_ID}"
- xcodebuild -version
# Pods/ is removed with the rest of the checkout's untracked files: reinstall it
- |
if [ -f "Podfile" ]; then
pod install
fi
# ---- BUILD ----
build:
stage: build
script:
- |
xcodebuild build \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify
# ---- TEST ----
unit_tests:
stage: test
needs: ["build"]
script:
- |
xcodebuild test \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-resultBundlePath "TestResults.xcresult" \
-parallel-testing-enabled YES \
| xcbeautify --report junit --report-path build/reports
artifacts:
when: always
paths:
- TestResults.xcresult/
reports:
junit: build/reports/junit.xml
expire_in: 7 days
after_script:
- xcrun simctl shutdown all 2>/dev/null || true
# ---- DEPLOY ----
deploy_testflight:
stage: deploy
needs: ["unit_tests"]
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- |
# Install or update Fastlane
which fastlane || brew install fastlane
# Run Fastlane beta lane
fastlane beta
environment:
name: testflight
variables:
MATCH_PASSWORD: ${MATCH_PASSWORD}
# Names read by fastlane's app_store_connect_api_key action (call it in your lane)
APP_STORE_CONNECT_API_KEY_KEY_ID: ${APP_STORE_KEY_ID}
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${APP_STORE_ISSUER_ID}
APP_STORE_CONNECT_API_KEY_KEY: ${APP_STORE_KEY_CONTENT}
Tambahkan Variabel CI/CD di GitLab
Navigasikan ke Settings > CI/CD > Variables di proyek GitLab Anda dan tambahkan variabel-variabel ini sebagai "Masked" dan "Protected":
MATCH_PASSWORD: Kata sandi untuk enkripsi Fastlane MatchAPP_STORE_KEY_ID: ID Kunci API App Store ConnectAPP_STORE_ISSUER_ID: Issuer ID App Store ConnectAPP_STORE_KEY_CONTENT: Isi file kunci .p8
Optimalkan Performa
Konfigurasi Cache GitLab
Di Mac yang persisten, cache tercepat adalah yang tidak perlu dipindahkan GitLab: pipeline di atas menyimpan DerivedData di luar checkout, sehingga tetap hangat di antara job. Dengan shell executor, cache GitLab sendiri dikompres dan diekstrak di setiap job, jadi gunakan untuk folder kecil seperti Pods/:
# DerivedData already stays on disk outside the checkout (DERIVED_DATA above).
# Use GitLab's cache for small folders, keyed on the lockfile:
build:
cache:
key:
files:
- Podfile.lock
paths:
- Pods/
policy: pull-push
# For test jobs that don't modify the cache, use pull-only:
unit_tests:
cache:
key:
files:
- Podfile.lock
paths:
- Pods/
policy: pull
Gunakan Artifact untuk Data Antar-Stage
# Useful when the build and test jobs can run on different Macs.
# build-for-testing compiles the app and its test bundles once; artifacts must
# sit inside the project folder, so this job builds there
build:
script:
- |
xcodebuild build-for-testing \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath build/DerivedData \
| xcbeautify
artifacts:
paths:
- build/DerivedData/Build/Products/
expire_in: 2 hours
# The test stage receives the built products and runs them without rebuilding
unit_tests:
needs: ["build"] # downloads the build job's artifacts
script:
- |
xcodebuild test-without-building \
-xctestrun build/DerivedData/Build/Products/*.xctestrun \
-destination "${DESTINATION}" \
| xcbeautify
Eksekusi Pengujian Paralel
# On one Mac, let xcodebuild split the test classes across simulator clones.
# (GitLab's parallel: keyword only helps if each copy runs a different part of
# the suite, using CI_NODE_INDEX and CI_NODE_TOTAL; otherwise it repeats it.)
unit_tests:
stage: test
script:
- |
xcodebuild test \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 4 \
| xcbeautify
Pembersihan Cache Terjadwal
# On the Mac Mini, set up a weekly cleanup cron job
crontab -e
# Add these lines:
# Every Sunday at 3 AM, remove DerivedData folders not used for 7 days
# (the pipeline keeps them in ~/builds/DerivedData, one per project and slot)
0 3 * * 0 find ~/builds/DerivedData -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec rm -rf {} + 2>/dev/null
# Checkouts in ~/builds are reused by the next job (git fetch): no need to delete them
# Clean Homebrew cache monthly
0 5 1 * * /opt/homebrew/bin/brew cleanup --prune=30 2>/dev/null
Pemecahan Masalah
Runner menampilkan "offline" di GitLab
Runner berjalan sebagai LaunchAgent, jadi mulai saat sesi pengguna Anda dimulai: setelah reboot, runner tetap offline sampai pengguna itu login. Di Mac headless, aktifkan Automatic login untuk pengguna ini (System Settings > Users & Groups; tidak tersedia selama FileVault aktif). Lalu periksa status layanan runner dan log-nya:
# Check service status
brew services list | grep gitlab-runner
# View logs
cat /opt/homebrew/var/log/gitlab-runner.log
# Restart the service
brew services restart gitlab-runner
# Verify connectivity to GitLab
gitlab-runner verify
Error permission denied saat build
Runner mungkin memerlukan akses ke direktori developer Xcode:
# Ensure the runner user has Xcode access (run once, in your SSH session)
sudo xcode-select -s /Applications/Xcode-27.0.0.app/Contents/Developer
sudo xcodebuild -license accept
# If using simulators, ensure the user can access them
xcrun simctl list devices
Cache tidak dipulihkan
Pastikan cache key konsisten dan path-nya ada:
# Check cache directory permissions
ls -la ~/builds/
# The shell executor stores caches locally by default
# Verify the cache directory in config.toml:
cat ~/.gitlab-runner/config.toml
# Ensure [runners.cache] section has the right settings
# For local caching (most efficient for persistent runners):
# [runners.cache]
# Type = "" # empty = local cache
Build Xcode macet atau timeout
Ini sering disebabkan oleh prompt akses keychain atau masalah simulator:
# Unlock the keychain before builds
security unlock-keychain -p "YOUR_PASSWORD" ~/Library/Keychains/login.keychain-db
# Kill stuck simulators
xcrun simctl shutdown all
pkill -f "Simulator.app" 2>/dev/null || true
# Set a build timeout in .gitlab-ci.yml
build:
timeout: 30 minutes
FAQ
Bisakah saya menggunakan Docker executor di macOS?
Bagaimana cara mendaftarkan runner untuk grup GitLab?
Haruskah saya menggunakan shell executor atau SSH executor?
Bisakah saya menjalankan runner GitLab dan GitHub Actions di Mac yang sama?
Bagaimana cara memperbarui GitLab Runner?
# Update via Homebrew
brew upgrade gitlab-runner
# Restart the service
brew services restart gitlab-runner
# Verify the new version
gitlab-runner --version