Langsung ke konten utama

Build dan Deploy Aplikasi iOS dengan Bitbucket Pipelines

Build, sign, dan kirim aplikasi iOS ke TestFlight dari Bitbucket Pipelines tanpa hosted macOS runners: cloud builds dari langkah Linux atau self-hosted Mac.

Bitbucket Pipelines membangun dan mengirim aplikasi iOS ke TestFlight

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 ios menyalin 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-bump jika 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_BASE64 variabel yang diamankan; --ios-provisioning-profile flag 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

  1. Di Bitbucket, buka Pengaturan repository > Runners (atau Pengaturan Workspace > Workspace runners untuk membagikannya) dan klik Tambahkan runner.
  2. Pilih MacOS sebagai sistem. Bitbucket menampilkan perintah start dengan UUID runner dan kredensial OAuth.
  3. Di Mac, instal JDK yang didukung (runner adalah program Java), Xcode 26, dan fastlane. Lalu tempel perintah start.
  4. 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.

Live updates untuk aplikasi Capacitor

Saat bug web-layer sudah live, kirim perbaikannya lewat Capgo alih-alih menunggu berhari-hari untuk persetujuan app store. Pengguna mendapat update di background, sementara perubahan native tetap mengikuti jalur review normal.

Terbaru dari Blog kami

Capgo memberikan insight terbaik yang Anda butuhkan untuk membuat aplikasi mobile yang benar-benar profesional.