跳转至正文
CI/CD iOS 教程

使用 Bitbucket Pipelines 构建并部署 iOS App

无需托管的 macOS runner,即可从 Bitbucket Pipelines 构建、签名并将 iOS 应用发布到 TestFlight:可通过 Linux 步骤或自托管 Mac 进行云端构建。

使用 Bitbucket Pipelines 构建并发布 iOS 应用到 TestFlight

Bitbucket Pipelines 无法在其托管的 runner 上构建 iOS 应用,因为每个步骤都在 Linux 容器中运行,而 Xcode 只能在 macOS 上运行。你有两种可行方案:将自己的 Mac 注册为自托管 Bitbucket runner,或者让步骤继续在 Linux 上运行,把 iOS 编译、签名和 TestFlight 上传交给云构建服务。本指南会介绍这两种方案,并给出完整的 bitbucket-pipelines.yml Capacitor 应用文件。

如果你的团队已经在 Bitbucket 里做 code 评审、关联 Jira、跑部署,那没必要为了 iOS 再引入一套 CI 工具。Pipeline 还是留在 Bitbucket,只需要把需要 Mac 的那部分搬走。

为什么 iOS 构建在 Bitbucket Pipelines 上会失败

Bitbucket Pipelines 会在你选定的 Docker 镜像(node:22, cimg/android等等)里运行每一步,底层是 Atlassian 的 Linux 基础设施。这对 bun install、单元测试、lint 检查,甚至完整的 Android 构建都没问题。但到了 iOS 就不行了:

  • xcodebuild, codesign,而 iOS SDK 只存在于 macOS 上。
  • Apple 的许可协议只允许 macOS 运行在 Apple 硬件上,所以没有任何 Linux 镜像能产出已签名的 .ipa.
  • 从 2026 年 4 月 28 日起,App Store Connect 会拒绝未使用 Xcode 26 和 iOS 26 SDK 构建的上传包,所以你用的 Mac 必须保持最新。详见 Apple 对 Capacitor 应用的 Xcode 26 要求.

Bitbucket 支持 Linux、Windows 和 macOS 的自托管 runner。Atlassian 不会对在自托管 runner 上运行的步骤计收构建分钟数,但硬件和维护成本要你自己承担。

你的三个选择

选项 iOS 构建运行位置 您需要维护的内容 适用场景
自托管 macOS Runner 您的 Mac mini 或租用的 Mac macOS、Xcode、钥匙串、证书、磁盘空间、正常运行时间 已拥有 Mac 且有人负责运维的团队
专为移动端设置的第二套 CI 其他供应商的 Mac 两条流水线,两套密钥和触发器 希望拥有独立移动 CI 的团队
从 Linux 步骤调用云端构建 Capgo Build 的 Mac 算力集群 只需一次 CLI 调用和几个安全变量 希望 Bitbucket 继续作为唯一 CI 的 Capacitor 团队

本指南的其余部分将详细介绍第一种和第三种方案。如需更全面地了解这些平台,请阅读 iOS 应用 CI/CD 平台对比.

方案 1:使用 Capgo Build 从 Linux 步骤构建 iOS

Capgo 构建 在 Capgo 管理的 Mac 上编译并签名 Capacitor 应用。您的 Bitbucket 步骤继续做它在 Linux 上本来就擅长的事:安装依赖、构建 Web 层并运行 cap sync ios。然后 Capgo CLI 会上传准备好的原生项目(而不是您的 Web 源码 code 或 node_modules),执行 Xcode 构建,用您的证书签名,并可将结果上传到 TestFlight。日志会实时回传到 Bitbucket 步骤,构建失败时命令会以非零状态码退出,因此 iOS 构建失败就会让流水线失败。

由于您的 runner 会把项目推送到 Capgo,Capgo 始终无需访问您的 Bitbucket 仓库。既没有需要授权的仓库连接,也没有需要批准的 OAuth 应用。

前提条件

  • 一个在本地构建的 Capacitor 应用(Capacitor 8 需要 Node.js 22 或更高版本)
  • 拥有团队管理员权限的 Apple Developer Program 会员资格
  • 一个 Capgo 账户和一个具有上传权限的 API 密钥
  • 在 Capgo 中注册的应用: bunx @capgo/cli@latest app add

本机一次性配置

只需运行一次 onboarding。在 iOS 上,它会创建或复用你的分发证书和描述文件,并请求一个 App Store Connect API 密钥:

bunx @capgo/cli@latest login
bunx @capgo/cli@latest build init --platform ios

如果你手边没有 Mac,仍然可以在浏览器中使用以下工具创建证书签名请求: iOS 证书生成器,或者遵循 在 Windows 上使用 Capgo Build 构建 iOS 应用.

在接触 CI 之前,先在笔记本电脑上运行一次构建。CI 不是调试首次构建的好地方:

bun run build
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release

导出 Bitbucket 凭据

CLI 会把凭据保存在你的本机。要移到 Bitbucket,请将它们导出到 .env 文件:

bunx @capgo/cli@latest build credentials manage --appId com.example.app --platform ios
# choose "Export to .env" in the menu

这会写入 .env.capgo.com.example.app.ios 模式为 0600。其中包含以下键:

变量 内容
BUILD_CERTIFICATE_BASE64 你的 ___ 的 Base64 编码 .p12 分发证书
P12_PASSWORD 证书密码(没有则留空)
CAPGO_IOS_PROVISIONING_MAP_BASE64 由 CLI 生成的 Provisioning profile 映射
APPLE_KEY_ID App Store Connect API 密钥 ID
APPLE_ISSUER_ID App Store Connect Issuer ID
APPLE_KEY_CONTENT 的 Base64 编码 .p8 API 密钥
APP_STORE_CONNECT_TEAM_ID 您的 Apple 团队 ID

将每一行添加为 Bitbucket 中的 受保护 变量,路径为 仓库设置 > 流水线 > 仓库变量,以及 CAPGO_TOKEN ,并填入您的 Capgo API 密钥。受保护变量在日志中会被掩码处理,且无法从 UI 中读取。

如果你更想用 API,这个循环会一次性创建它们。它使用带 pipeline variables 写入权限的 Bitbucket API token:

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"

之后删除该文件并添加 .env.capgo.* 改为 .gitignore。它保存着明文签名材料。

对于生产环境凭据,建议使用 部署变量 而不是仓库变量。它们的作用域限定在某个部署环境(例如 Production),在 Premium 套餐中,你可以将该环境限制为仅限管理员或特定分支访问。

pipeline 文件

Bitbucket 会把仓库变量和部署变量注入为环境变量,因此 Capgo CLI 无需任何映射就能直接读取。该文件会在每次 push 时运行测试,按需构建 ad hoc iOS 包,并在打版本 tag 时发布到 TestFlight:

# 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

几个关键细节:

  • 该镜像就是一个普通 Linux 镜像 node:22。这个文件里没有任何步骤需要 macOS。
  • 在 Linux 上, cap sync ios 会复制 Web 资源并更新原生配置,但无法运行 CocoaPods。这没关系。执行构建的 Mac 会自行安装 pods 或解析 Swift packages。
  • Capgo 会从 App Store Connect 读取最新 build number 并递增,因此你永远不会遇到“bundle version must be higher than the previously uploaded version”。请传入 --skip-build-number-bump (如果你自行管理)。
  • Ad Hoc build 需要 Ad Hoc provisioning profile,它只能安装到其列出的设备上。请将其存为安全 ADHOC_PROFILE_BASE64 变量; --ios-provisioning-profile 该 flag 仅对本次 build 覆盖环境中的 App Store profile。使用以下方式收集测试人员的 UDID: iOS UDID 查询工具.
  • 将 CLI 固定到确切版本(例如 @capgo/cli@7.104.0),等你的 pipeline 稳定后,CLI 的发布就不会在 sprint 中途改变行为。

从以下位置运行自定义 pipeline: Pipelines > 运行 pipeline > ios-adhoc最后一条命令会输出一个下载链接,该链接在保留期结束后失效。

TestFlight 前手动审批

Bitbucket 支持 trigger: manual 在单个步骤中。如果需要人工审批每次 TestFlight 上传,请将发布流程拆分为构建步骤和受控上传步骤:

  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

要在 TestFlight 上传后直接进入 App Review,请添加 --submit-to-store-review 配合 --store-release-name "$BITBUCKET_TAG"。您的 App Store 列表、截图和合规性回答必须先在 App Store Connect 中填写完整。

并行添加 Android

Android 构建可以直接在 Bitbucket 的 Linux runner 上运行,但您也可以将其发送到 Capgo Build,以便两个平台使用相同的签名和上传流程。使用一个 parallel 代码块中:

  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

Android 步骤需要 ANDROID_KEYSTORE_FILE, KEYSTORE_KEY_ALIAS, KEYSTORE_KEY_PASSWORD, KEYSTORE_STORE_PASSWORD,以及 PLAY_CONFIG_JSON 作为安全变量。以相同方式导出它们: --platform android.

无需 Native Build 即可发布 Web 端变更

大多数 Capacitor 应用的提交只涉及 HTML、CSS 和 JavaScript,这些改动不需要新的二进制文件。通过 Capgo 实时更新,Bitbucket 步骤可以在商店构建和 OTA bundle 之间做选择:

  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 ,不需要 Native Build 时以 code 0 退出,需要时以 1 退出。它会把原生插件版本与 channel 上当前运行的版本做对比。它看不到 ios/下的手动修改,所以这些文件变化时也要强制走原生构建路径。完整写法见 自动选择 live update 或 Native Build.

方案 2:自托管 macOS runner

如果你已经有 Mac,可以把其中一台注册为 Bitbucket runner,自己跑 Xcode。

注册 runner

  1. 在 Bitbucket 中打开 仓库设置 > Runners (或 工作区设置 > 工作区 Runner 以共享它)并点击 添加 Runner.
  2. 选择 MacOS 作为系统。Bitbucket 会显示一条启动命令,包含 Runner UUID 和 OAuth 凭据。
  3. 在 Mac 上安装受支持的 JDK(Runner 是一个 Java 程序)、Xcode 26 和 fastlane。然后粘贴启动命令。
  4. 将 Runner 作为 launch agent 保持运行,以便在重启后继续工作。

步骤通过标签指定目标 Runner:

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

macOS runner 直接在宿主机上以你的用户环境运行,而非在容器中。请在 Mac 上安装 Bun 和 Ruby。

精简版 Fastfile

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

新的 Capacitor 8 项目使用 Swift Package Manager,因此你构建的是 App.xcodeproj。仍使用 CocoaPods 的旧项目构建的是 App.xcworkspace ,并且需要 pod install 在 runner 上。我们的 GitHub Actions iOS 指南 详细解释了每个 fastlane action;Bitbucket 上的 lane 配置相同。

注册后你将获得什么

Archive 是最简单的部分。自托管 Mac 还意味着:

  • Xcode 升级 每当 Apple 提高最低 SDK 要求时,都能在截止日期前完成测试。
  • Keychain 状态。 崩溃的任务可能会遗留临时 Keychain,导致下一个任务使用错误的身份。
  • 无隔离。 任务运行在同一块磁盘上。DerivedData、旧归档和模拟器会把它占满。
  • 一次一个构建 每个 runner,除非你买更多 Mac。
  • 证书和描述文件 每年都会过期,还得在机器上续期。

如果你的团队本来就在管理 Mac,这是个合理的取舍。如果没人愿意负责这台机器,方案 1 可以省掉它。

故障排除

现象 可能原因和修复方法
CAPGO_TOKEN is not set 变量缺失,或者它是部署变量,而该步骤没有 deployment: key。
步骤作用于 main 但不适用于标签 部署变量仅适用于声明了该部署环境的步骤。
Provisioning profile doesn't match bundle ID 该配置文件是为另一个 App ID 创建的。请重新运行 build init 并重新导出。
App Store Connect 身份验证失败 Issuer ID 或 Key ID 错误,或密钥已被吊销。请在 App Store Connect > 用户和访问 > 集成 中检查密钥。
SDK 版本上传被拒绝 构建使用了低于 26 版本的 Xcode。请升级自托管 Mac。
自托管步骤卡在“Pending”状态 Runner 离线或标签不匹配 self.hosted 以及 macos.
errSecInternalComponent 在 codesign 期间 在非交互式会话中,钥匙串已锁定。请使用 setup_ci 并将证书导入临时钥匙串。
构建通过但 TestFlight 中没有内容 处理需要几分钟,且出口合规性可能处于待定状态。如果 ITSAppUsesNonExemptEncryption 改为 Info.plist 如果您的应用仅使用标准加密,请添加

更多关于修复失败流水线的方案见 修复 Capacitor CI/CD 流水线中的构建失败 以及 Capgo 构建故障排除指南.

该如何选择?

如果您已拥有硬件、需要完全控制 Xcode 环境,或构建非 Capacitor 应用的原生 Swift 目标,请选择自托管 Mac。如果您的应用是 Capacitor 应用,且希望 Bitbucket 成为团队配置 CI 的唯一平台,请从 Linux 步骤中选择云构建。无论哪种方式,请将构建逻辑保留在 bitbucket-pipelines.yml 与您的 code 放在一起,将签名材料存储在安全变量中,并且仅在原生 code 实际发生变化时才运行原生构建。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。