Bitbucket Pipelines tidak dapat membangun aplikasi iOS di runner hosted-nya, karena setiap langkah berjalan di container Linux dan Xcode hanya berjalan di macOS. Anda memiliki dua opsi yang berfungsi: daftarkan Mac Anda sendiri sebagai runner Bitbucket self-hosted, atau biarkan langkah di Linux dan serahkan kompilasi iOS, penandatanganan, dan upload TestFlight ke layanan build cloud. Panduan ini menunjukkan keduanya, dengan lengkap bitbucket-pipelines.yml file untuk aplikasi Capacitor.
Jika tim Anda sudah menggunakan Bitbucket untuk review code, tautan Jira, dan deployment, Anda tidak perlu alat CI kedua hanya untuk iOS. Pipeline tetap di Bitbucket. Hanya bagian yang membutuhkan Mac yang dipindahkan.
Mengapa build iOS gagal di Bitbucket Pipelines
Bitbucket Pipelines menjalankan setiap langkah di dalam image Docker yang Anda pilih (node:22, cimg/android, dan sebagainya) di infrastruktur Linux Atlassian. Itu tidak masalah untuk bun install, unit tests, linting, dan bahkan build Android penuh. Berhenti di iOS:
xcodebuild,codesign, dan SDK iOS hanya ada di macOS.- Lisensi Apple hanya mengizinkan macOS berjalan di perangkat keras Apple, sehingga tidak ada image Linux yang dapat menghasilkan
.ipa. - Sejak 28 April 2026, App Store Connect menolak upload yang tidak di-build dengan Xcode 26 dan SDK iOS 26, jadi Mac apa pun yang Anda pakai harus tetap terkini. Lihat Persyaratan Xcode 26 dari Apple untuk app Capacitor.
Bitbucket mendukung self-hosted runner di Linux, Windows, dan macOS. Atlassian tidak menagih build minutes untuk langkah yang berjalan di runner Anda sendiri, tetapi Anda membayarnya lewat hardware dan maintenance.
Tiga opsi Anda
| Opsi | Di mana build iOS berjalan | Apa yang Anda maintain | Cocok untuk |
|---|---|---|---|
| Runner macOS self-hosted | Mac mini Anda atau Mac sewaan | macOS, Xcode, keychain, sertifikat, ruang disk, uptime | Tim yang sudah punya Mac dan ada orang untuk mengelolanya |
| CI kedua khusus untuk mobile | Mac dari vendor lain | Dua pipeline, dua set secrets dan triggers | Tim yang memang ingin CI mobile khusus |
| Cloud build yang dipanggil dari langkah Linux | Fleet Mac dari Capgo Build | Satu panggilan CLI dan beberapa variabel aman | Tim Capacitor yang ingin Bitbucket tetap menjadi satu-satunya CI |
Sisanya dari panduan ini membahas opsi pertama dan ketiga secara detail. Untuk gambaran lebih luas tentang platformnya, baca Membandingkan platform CI/CD untuk aplikasi iOS.
Opsi 1: Build iOS dari langkah Linux dengan Capgo Build
Build Capgo mengompilasi dan menandatangani aplikasi Capacitor di Mac yang dikelola Capgo. Langkah Bitbucket Anda melakukan apa yang sudah擅长的 di Linux: menginstal dependensi, membangun lapisan web, dan menjalankan cap sync ios. Kemudian Capgo CLI mengunggah proyek native yang sudah disiapkan (bukan sumber web Anda code atau node_modules), menjalankan build Xcode, menandatanganinya dengan sertifikat Anda, dan dapat mengunggah hasilnya ke TestFlight. Logs mengalir kembali ke langkah Bitbucket, dan perintah keluar dengan non-zero jika build gagal, sehingga build iOS yang gagal akan membuat pipeline gagal.
Karena runner Anda mendorong proyek ke Capgo, Capgo tidak pernah memerlukan akses ke repositori Bitbucket Anda. Tidak ada koneksi repositori yang perlu diotorisasi dan tidak ada aplikasi OAuth yang perlu disetujui.
Prerequisites
- Aplikasi Capacitor yang dapat dibangun secara lokal (Capacitor 8 membutuhkan Node.js 22 atau lebih baru)
- Keanggotaan Apple Developer Program dengan hak admin di tim
- Akun Capgo dan kunci API dengan izin upload
- Aplikasi terdaftar di Capgo:
bunx @capgo/cli@latest app add
Setup sekali di mesin Anda
Jalankan onboarding sekali. Di iOS, ini membuat atau menggunakan ulang sertifikat distribusi dan profil provisioning Anda, serta meminta kunci App Store Connect API:
bunx @capgo/cli@latest login
bunx @capgo/cli@latest build init --platform ios
Jika tidak ada Mac di tangan, Anda tetap bisa membuat certificate signing request di browser dengan iOS certificate generator kami, atau ikuti Build aplikasi iOS dari Windows dengan Capgo Build.
Jalankan satu build dari laptop Anda sebelum menyentuh CI. CI adalah tempat yang buruk untuk debug build pertama:
bun run build
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
Ekspor kredensial untuk Bitbucket
CLI menyimpan kredensial di mesin Anda. Untuk memindahkannya ke Bitbucket, ekspor kredensial tersebut ke file dotenv:
bunx @capgo/cli@latest build credentials manage --appId com.example.app --platform ios
# choose "Export to .env" in the menu
Ini menulis .env.capgo.com.example.app.ios dengan mode 0600. Berisi kunci-kunci ini:
| Variabel | Isi |
|---|---|
BUILD_CERTIFICATE_BASE64 |
Base64 dari .p12 sertifikat distribusi Anda |
P12_PASSWORD |
Kata sandi sertifikat (kosong jika tidak ada) |
CAPGO_IOS_PROVISIONING_MAP_BASE64 |
Pemetaan profil provisioning yang dihasilkan oleh CLI |
APPLE_KEY_ID |
ID kunci API App Store Connect |
APPLE_ISSUER_ID |
ID penerbit App Store Connect |
APPLE_KEY_CONTENT |
Base64 dari .p8 key API |
APP_STORE_CONNECT_TEAM_ID |
ID tim Apple Anda |
Tambahkan setiap baris sebagai terlindungi variabel di Bitbucket di bawah Pengaturan repository > Pipelines > Variabel repository, serta CAPGO_TOKEN dengan kunci Capgo API Anda. Variabel terlindungi akan disamarkan di logs dan tidak dapat dibaca kembali dari UI.
Jika Anda lebih memilih API, loop ini membuatnya sekaligus. Ini menggunakan token API Bitbucket dengan scope tulis variabel pipeline:
WORKSPACE=my-team
REPO=my-app
ENV_FILE=.env.capgo.com.example.app.ios
grep -v '^#' "$ENV_FILE" | grep '=' | while IFS= read -r line; do
key="${line%%=*}"
value="${line#*=}"
value="${value%\"}"; value="${value#\"}"
jq -n --arg k "$key" --arg v "$value" '{key:$k, value:$v, secured:true}' |
curl -s -u "$BB_EMAIL:$BB_API_TOKEN" \
-H 'Content-Type: application/json' \
-X POST --data @- \
"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO/pipelines_config/variables" > /dev/null
echo "created $key"
done
rm "$ENV_FILE"
Hapus file tersebut kemudian dan tambahkan .env.capgo.* ke .gitignore. File ini berisi materi penandatanganan plaintext.
Untuk kredensial produksi, pertimbangkan variabel deployment bukan variabel repository. Mereka memiliki cakupan pada lingkungan deployment (misalnya Production), dan Anda dapat membatasi lingkungan tersebut hanya untuk admin atau branch tertentu di paket Premium.
File pipeline
Bitbucket menyuntikkan variabel repository dan deployment sebagai environment variables, sehingga Capgo CLI mengambilnya tanpa perlu mapping. File ini menjalankan test di setiap push, membangun iOS build ad hoc saat diminta, dan mengirim ke TestFlight pada version tags:
# bitbucket-pipelines.yml
image: node:22
definitions:
caches:
bun: ~/.bun/install/cache
pipelines:
default:
- step:
name: Lint and test
caches:
- bun
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile
- bun run lint
- bun run test
tags:
'v*':
- step:
name: Build iOS and upload to TestFlight
deployment: Production
caches:
- bun
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile
- bun run build
- bunx cap sync ios
- >
bunx @capgo/cli@latest build request com.example.app
--platform ios
--build-mode release
--ios-distribution app_store
custom:
ios-adhoc:
- step:
name: Ad hoc iOS build for testers
caches:
- bun
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile
- bun run build
- bunx cap sync ios
- echo "$ADHOC_PROFILE_BASE64" | base64 --decode > adhoc.mobileprovision
- >
bunx @capgo/cli@latest build request com.example.app
--platform ios
--build-mode release
--ios-distribution ad_hoc
--ios-provisioning-profile ./adhoc.mobileprovision
--output-upload
--output-retention 2d
--output-record /tmp/build.json
- bunx @capgo/cli@latest build last-output --path /tmp/build.json --field outputUrl
Beberapa detail yang penting:
- Gambar ini adalah Linux biasa
node:22. Tidak ada yang perlu macOS di file ini. - Di Linux,
cap sync iosmenyalin aset web dan memperbarui konfigurasi native tetapi tidak dapat menjalankan CocoaPods. Tidak masalah. Mac yang menjalankan build akan menginstal pods atau menyelesaikan Swift packages sendiri. - Capgo membaca nomor build terbaru dari App Store Connect dan meningkatkannya, sehingga Anda tidak akan pernah menemui “bundle version must be higher than the previously uploaded version”. Lewati
--skip-build-number-bumpjika Anda mengelolanya sendiri. - Build ad hoc membutuhkan profil provisioning Ad Hoc, yang hanya dapat diinstal pada perangkat yang terdaftar di dalamnya. Simpan profil tersebut sebagai file yang aman
ADHOC_PROFILE_BASE64variabel yang diamankan;--ios-provisioning-profileflag menimpa profil App Store dari environment hanya untuk build ini. Kumpulkan UDID tester dengan Pencari UDID iOS. - Pin CLI ke versi yang tepat (misalnya
@capgo/cli@7.104.0) setelah pipeline stabil, agar rilis CLI tidak mengubah perilaku di tengah sprint.
Jalankan pipeline kustom dari Pipelines > Run pipeline > ios-adhoc. Perintah terakhir mencetak tautan download yang kedaluwarsa setelah masa retensi berakhir.
Persetujuan manual sebelum TestFlight
Bitbucket mendukung trigger: manual pada sebuah step. Pisahkan rilis menjadi step build dan step upload yang di-gate jika Anda ingin setiap upload TestFlight disetujui manual:
tags:
'v*':
- step:
name: Test
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile
- bun run test
- step:
name: Ship iOS to TestFlight
trigger: manual
deployment: Production
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile
- bun run build
- bunx cap sync ios
- bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
Untuk langsung masuk ke App Review setelah upload TestFlight, tambahkan --submit-to-store-review dengan --store-release-name "$BITBUCKET_TAG". Listing App Store, screenshot, dan jawaban kepatuhan Anda tetap harus lengkap di App Store Connect terlebih dahulu.
Tambahkan Android secara paralel
Build Android bisa dijalankan langsung di runner Linux Bitbucket, tetapi Anda juga bisa mengirimnya ke Capgo Build agar kedua platform memakai alur signing dan upload yang sama. Gunakan parallel blok:
tags:
'v*':
- parallel:
- step:
name: iOS release
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile && bun run build
- bunx cap sync ios
- bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
- step:
name: Android release
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile && bun run build
- bunx cap sync android
- bunx @capgo/cli@latest build request com.example.app --platform android --build-mode release --android-track internal
Step Android membutuhkan ANDROID_KEYSTORE_FILE, KEYSTORE_KEY_ALIAS, KEYSTORE_KEY_PASSWORD, KEYSTORE_STORE_PASSWORD, dan PLAY_CONFIG_JSON sebagai variabel aman. Ekspor dengan cara yang sama menggunakan --platform android.
Rilis perubahan web-only tanpa native build
Sebagian besar commit ke aplikasi Capacitor hanya menyentuh HTML, CSS, dan JavaScript. Hal-hal tersebut tidak memerlukan binary baru. Dengan Live updates Capgo, langkah Bitbucket dapat memutuskan antara build store dan bundle over-the-air:
branches:
main:
- step:
name: Release (live update or native)
script:
- curl -fsSL https://bun.sh/install | bash
- export PATH="$HOME/.bun/bin:$PATH"
- bun install --frozen-lockfile
- bun run build
- |
if bunx @capgo/cli@latest build needed com.example.app --channel production; then
echo "No native change, shipping a live update"
bunx @capgo/cli@latest bundle upload com.example.app --channel production
else
echo "Native change detected, building iOS"
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release
fi
build needed berakhir dengan code 0 jika native build tidak diperlukan, dan 1 jika diperlukan. Ia membandingkan versi plugin native dengan yang aktif di channel. Ia tidak melihat edit manual di bawah ios/, jadi paksa juga jalur native saat file tersebut berubah. Pola lengkapnya ada di Pilih otomatis live update atau native build.
Opsi 2: Runner macOS self-hosted
Jika Anda sudah memiliki Mac, Anda dapat mendaftarkannya sebagai runner Bitbucket dan menjalankan Xcode sendiri.
Daftarkan runner
- Di Bitbucket, buka Pengaturan repository > Runners (atau Pengaturan Workspace > Workspace runners untuk membagikannya) dan klik Tambahkan runner.
- Pilih MacOS sebagai sistem. Bitbucket menampilkan perintah start dengan UUID runner dan kredensial OAuth.
- Di Mac, instal JDK yang didukung (runner adalah program Java), Xcode 26, dan fastlane. Lalu tempel perintah start.
- Jalankan runner sebagai launch agent agar tetap aktif setelah reboot.
Sebuah step menargetkan runner dengan label:
pipelines:
tags:
'v*':
- step:
name: iOS build on our Mac
runs-on:
- self.hosted
- macos
script:
- bun install --frozen-lockfile
- bun run build
- bunx cap sync ios
- echo "$BUILD_CERTIFICATE_BASE64" | base64 --decode > dist.p12
- echo "$PROFILE_BASE64" | base64 --decode > profile.mobileprovision
- bundle exec fastlane ios beta
Runner macOS dieksekusi langsung di host dengan lingkungan pengguna Anda, bukan di dalam container. Instal Bun dan Ruby di Mac itu sendiri.
Fastfile minimal
default_platform(:ios)
platform :ios do
lane :beta do
setup_ci(force: true)
api_key = app_store_connect_api_key(
key_id: ENV["APPLE_KEY_ID"],
issuer_id: ENV["APPLE_ISSUER_ID"],
key_content: ENV["APPLE_KEY_CONTENT"],
is_key_content_base64: true
)
import_certificate(
certificate_path: "dist.p12",
certificate_password: ENV["P12_PASSWORD"],
keychain_name: "fastlane_tmp_keychain",
keychain_password: ""
)
install_provisioning_profile(path: "profile.mobileprovision")
update_code_signing_settings(
path: "ios/App/App.xcodeproj",
use_automatic_signing: false,
team_id: ENV["APP_STORE_CONNECT_TEAM_ID"],
code_sign_identity: "Apple Distribution",
profile_name: ENV["PROFILE_NAME"]
)
increment_build_number(
xcodeproj: "ios/App/App.xcodeproj",
build_number: latest_testflight_build_number(api_key: api_key) + 1
)
build_app(
project: "ios/App/App.xcodeproj", # use workspace: "ios/App/App.xcworkspace" for CocoaPods projects
scheme: "App",
export_method: "app-store"
)
upload_to_testflight(api_key: api_key, skip_waiting_for_build_processing: true)
end
end
Proyek Capacitor 8 baru menggunakan Swift Package Manager, jadi Anda build App.xcodeproj. Proyek lama yang masih menggunakan CocoaPods build App.xcworkspace dan membutuhkan pod install di runner. Panduan GitHub Actions iOS kami menjelaskan setiap aksi fastlane secara lebih mendalam; lane-nya sama di Bitbucket.
Untuk apa Anda mendaftar
Membuat archive adalah bagian yang mudah. Mac self-hosted juga berarti:
- Upgrade Xcode setiap kali Apple menaikkan SDK minimum, diuji sebelum tenggat waktu.
- Status Keychain. Job yang crash bisa meninggalkan keychain sementara, dan job berikutnya akan mengambil identitas yang salah.
- Tidak ada isolasi. Job berjalan di disk yang sama. DerivedData, archive lama, dan simulator akan mengisinya penuh.
- Satu build pada satu waktu per runner, kecuali Anda membeli Mac lebih banyak.
- Sertifikat dan profil yang kedaluwarsa setiap tahun dan harus diperbarui di mesin tersebut.
Ini trade-off yang wajar jika tim Anda sudah mengelola Mac. Jika tidak ada yang mau mengurus mesin itu, Opsi 1 menghilangkannya.
Pemecahan Masalah
| Gejala | Penyebab dan solusi yang mungkin |
|---|---|
CAPGO_TOKEN is not set |
Variabel tidak ada, atau itu deployment variable dan step tidak memiliki deployment: key. |
Step berfungsi di main tapi tidak di tags |
Deployment variables hanya berlaku untuk step yang mendeklarasikan deployment environment tersebut. |
Provisioning profile doesn't match bundle ID |
Profil dibuat untuk app ID lain. Jalankan ulang build init dan ekspor ulang. |
| Autentikasi App Store Connect gagal | Issuer ID atau key ID salah, atau key sudah dicabut. Periksa key di App Store Connect > Users and Access > Integrations. |
| Upload ditolak untuk versi SDK | Build menggunakan Xcode versi lebih lama dari 26. Upgrade Mac self-hosted. |
| Langkah self-hosted macet di “Pending” | Runner offline atau label tidak cocok self.hosted dan macos. |
errSecInternalComponent selama codesign |
Keychain terkunci dalam sesi non-interaktif. Gunakan setup_ci dan impor sertifikat ke keychain sementara. |
| Build lolos tapi tidak ada di TestFlight | Pemrosesan memakan waktu beberapa menit, dan export compliance mungkin masih pending. Tambahkan ITSAppUsesNonExemptEncryption ke Info.plist jika aplikasi Anda hanya menggunakan enkripsi standar. |
Perbaikan lebih lanjut untuk pipeline yang gagal ada di Mengatasi kegagalan build di pipeline CI/CD Capacitor dan Panduan troubleshooting Capgo Build.
Opsi mana yang harus dipilih?
Pilih Mac self-hosted jika Anda sudah memiliki hardware, membutuhkan kontrol penuh atas lingkungan Xcode, atau membangun target Swift native yang bukan aplikasi Capacitor. Pilih cloud build dari langkah Linux jika aplikasi Anda adalah aplikasi Capacitor dan Anda ingin Bitbucket tetap menjadi satu-satunya tempat tim Anda mengonfigurasi CI. Dalam hal apa pun, simpan logika build di bitbucket-pipelines.yml di samping code Anda, simpan signing material di variabel yang terlindungi, dan hanya jalankan build native saat code native benar-benar berubah.