Offer

Xcode Product → Archive: The Full Pipeline (and Why CI Keeps Sticking There)

CI Xcode Archive · iOS Release
2026-06-15 ~14 min read

Locally, one click on Product → Archive and the Organizer exports your IPA. In CI, xcodebuild archive often hangs for 20 minutes, times out, or fails silently at the same stage. The gap is not whether you know Xcode — it is whether you treat Archive as an observable pipeline instead of a black-box command.

This article walks through each stage: what Build → Archive → Sign → Export actually does, how xcodebuild reproduces the menu action, and a seven-category symptom-to-action table for the failures iOS and Flutter teams hit most often on GitHub Actions and self-hosted Mac runners.

Xcode Archive pipeline and CI workflow diagram
Local Archive gives you GUI feedback; in CI the same chain needs log segmentation — otherwise you only see "job ran 40 minutes."

Key takeaways

  1. Archive ≠ Build: Archive always runs Release + Any iOS Device and produces a .xcarchive. A passing local Debug build does not mean Archive will pass.
  2. CI stalls cluster in four segments: Scheme/config, compile cache, signing & keychain, and exportArchive — locate the segment in logs before you fix anything.
  3. xcodebuild archive is equivalent to the menu action, but CI must explicitly pass -scheme, -configuration Release, and -destination 'generic/platform=iOS'.
  4. "Stuck with no output" usually means waiting on keychain authorization, waiting on provisioning profile download, or memory swap — not a dead xcodebuild process.
  5. Self-hosted cloud Mac runners with persistent DerivedData and a dedicated signing keychain are often an order of magnitude more stable than cold Archive on ephemeral hosted runners.

1. Archive vs Build: what actually changes

Many teams summarize CI failures as "xcodebuild broke." In reality, Cmd+B Build and Product → Archive are two different paths:

Dimension Build (⌘B) Archive
Typical Configuration Debug (local dev) Release (App Store / TestFlight)
Destination Simulator or connected device Any iOS Device (arm64)
Output .app in DerivedData .xcarchive + exportable IPA
Signing Development cert may compile some targets Distribution cert + provisioning profile required
Optimization Low — fast compile Full optimization — significantly longer compile

So "3 minutes locally" and "20 minutes in CI then failure" are not contradictory: you may be running Debug + simulator locally while CI runs Release + device architecture + full signing chain. Align what you compare before you optimize. For compile-time gaps specifically, see why xcodebuild is 2–3× slower in CI than local.

2. From menu to CLI: the Product → Archive equivalent

Clicking Product → Archive in the Xcode GUI roughly maps to:

  1. Select a Shared Scheme with Release configuration;
  2. Set Destination to Any iOS Device (not a simulator);
  3. Run xcodebuild archive and write output to ARCHIVE_PATH;
  4. (Optional) Distribute App in Organizer → equivalent to xcodebuild -exportArchive.

A minimal Archive command for CI (adjust paths for your project):

xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
  CODE_SIGN_STYLE=Manual \
  DEVELOPMENT_TEAM=XXXXXXXXXX \
  | tee archive.log

Three parameters teams often miss:

  • -destination 'generic/platform=iOS': without it, xcodebuild may default to a simulator — Archive fails or behaves unpredictably.
  • -archivePath: must be writable; permission issues on hosted runner temp dirs cause "compiled but failed to write archive."
  • Scheme must be Shared and committed to Git: an unshared local Scheme does not exist after CI checkout → scheme not found.

Official references: Apple — Building your app, Distributing your app.

3. Four-stage pipeline at a glance

┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ 1. Resolve   │ →  │ 2. Archive   │ →  │ 3. Sign      │ →  │ 4. Export    │
│ SPM/Pods     │    │ Release full │    │ Certs/profiles│    │ IPA / Upload │
│ DerivedData  │    │ → .xcarchive │    │ Keychain auth │    │ TestFlight   │
└──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘
     pod install          xcodebuild           codesign           exportArchive
     often 5–15 min       archive 8–25 min     silent hang zone   plist errors common

When CI "gets stuck here," ask first: which stage does the last log line belong to? Stopping at CompileSwift is compile; at CodeSign is signing; at exportArchive is export config. Mixing stages leads to endless cache clears.

4. Stage 1: Resolve & Compile (why CI is slower)

Dependency resolution and compilation before Archive are usually "warm" locally and "cold" in CI:

  • CocoaPods / SPM: CI runs pod install from scratch every job — logs full of Installing … and 10 minutes gone before Archive starts. See Flutter iOS CI cache propagation.
  • Cold DerivedData: full Release compile with no cache means every Pod native module recompiles. Pinning DERIVED_DATA_PATH on an SSD-backed self-hosted runner often cuts the second Archive in half.
  • Memory pressure: Archive peaks higher than Debug; a 16 GB machine running parallel jobs swaps — CPU stays low but nothing moves. See runner memory and swap governance.

Quick check: if logs show heavy CompileC / SwiftCompile for third-party pods you did not touch, fix cache first — not signing.

5. Stage 2: Archive (generating .xcarchive)

After a successful xcodebuild archive, you should see this structure at -archivePath:

MyApp.xcarchive/
  Info.plist
  Products/Applications/MyApp.app
  dSYMs/...

Common failure modes:

  • Scheme Archive action has no target checked: a different Scheme builds locally, but the CI Scheme archives nothing.
  • Multi-target / extension misconfiguration: main app passes, Notification Service Extension signing fails, whole Archive fails.
  • Build number / version conflict: CI does not bump CFBundleVersion; upload fails later but gets blamed on "slow Archive."

Validate immediately: run ls -la "$ARCHIVE_PATH" and plutil -p "$ARCHIVE_PATH/Info.plist" to confirm ApplicationProperties exists — do not wait for export failure to discover an incomplete archive.

6. Stage 3: Code Sign (the #1 CI pitfall)

Local Archive may prompt for keychain access; you click "Always Allow" and the pipeline moves on. Unattended CI has no such step, so:

  • errSecInternalComponent, User interaction is not allowed: private key lives in the login keychain; CI user has no GUI authorization.
  • Expired profile / Bundle ID mismatch: log stops at CodeSign with Provisioning profile … doesn't match.
  • Enabling -allowProvisioningUpdates by mistake: requires Apple ID interactive login; headless environments wait forever.
  • Keychain not unlocked: job is missing security unlock-keychain / set-key-partition-list at the start.

Recommended on cloud Mac / self-hosted runners: create a dedicated keychain file (not the login keychain), import the distribution certificate + private key, unlock and set it as default at job start. Full chain: cloud Mac iOS CI: codesign and keychain boundaries.

# Job 开头示意(口令走 CI Secret)
KEYCHAIN=$RUNNER_TEMP/ci-signing.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security import dist.p12 -k "$KEYCHAIN" -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security list-keychains -s "$KEYCHAIN" login.keychain-db

If you use fastlane match or Xcode Cloud for credentials, the principle is unchanged: the CI process must access the private key without interaction — not merely "the cert is on the machine somewhere." See fastlane match.

7. Stage 4: Export IPA (exportArchive)

Archive only produces .xcarchive; TestFlight upload still needs Export. Distribute App in the menu maps to:

xcodebuild -exportArchive \
  -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
  -exportPath "$RUNNER_TEMP/export" \
  -exportOptionsPlist ExportOptions.plist

The method key in ExportOptions.plist sets export type (app-store, ad-hoc, development, etc.). Common CI issues:

  • method mismatches profile type: development profile with app-store method.
  • Missing teamID / signingCertificate: Manual signing errors surface at export, mistaken for slow Archive.
  • Upload bundled with export: altool / Transporter network latency counted as "Archive timeout."

Split archive, export, and upload into three timed steps. Upload via xcrun altool --upload-app or xcrun notarytool (macOS distribution) — decoupled from Archive.

8. Seven root causes: symptom / stage / action

Symptom / log keyword Stage First action
Long silence, low CPU Sign Check keychain unlock; disable GUI-dependent provisioning auto-update
scheme 'Foo' not found Pre-flight Mark Scheme Shared and commit; verify with -list in CI
No profiles for … were found Sign Verify Bundle ID; confirm profiles in repo or match fetch succeeded
Heavy CompileC on untouched Pods Compile Persist DerivedData; avoid clean inside the job
pod install > 8 minutes Resolve Cache ios/Pods; use pod install --deployment
Job killed by platform (no stack trace) Global Raise timeout; check swap / disk full (disk governance)
exportArchive failed + plist errors Export Run export in isolation; verify ExportOptions.plist method

On call: scan logs from the bottom for the first error:, then match the table — faster than a blanket flutter clean.

9. Troubleshooting runbook: segment first, fix second

  1. Reproduce Release Archive locally: Product → Scheme → Edit Scheme → Archive uses Release; Destination is Any iOS Device. If local fails, do not burn CI minutes.
  2. Add four-segment timing in CI: pod installarchiveexportArchiveupload; write each duration to the step summary.
  3. Keep full logs: avoid overly quiet log prettifiers; signing failures need raw CodeSign lines.
  4. Pin Xcode version: lock path with xcode-select or sudo xcode-select -s; align with .xcode-version or workflow macos-15.
  5. 48-hour validation: Archive the same commit twice; the second run should be noticeably faster (cache hit). If not, check memory and disk.

For release sprints or temporary build capacity, consider short-term remote Mac for release sprints instead of fighting Archive timeouts on ephemeral hosted runners.

10. GitHub Actions template

jobs:
  archive-ios:
    runs-on: [self-hosted, macOS, ios]  # 或 macos-14 托管机
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4

      - name: Install pods
        run: pod install --deployment
        working-directory: ios

      - name: Archive
        run: |
          set -o pipefail
          xcodebuild archive \
            -workspace ios/MyApp.xcworkspace \
            -scheme MyApp \
            -configuration Release \
            -destination 'generic/platform=iOS' \
            -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
            | tee archive.log
        env:
          DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer

      - name: Export IPA
        run: |
          xcodebuild -exportArchive \
            -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
            -exportPath "$RUNNER_TEMP/export" \
            -exportOptionsPlist ios/ExportOptions.plist

For self-hosted runner label routing, launchd keepalive, and CI user isolation, see Mac mini self-hosted runner setup and Flutter + GitHub Actions architecture overview.

11. Conclusion

Product → Archive is not "build a bit longer" — it is the full chain of Release compile + archive + distribution signing + optional export. When CI stalls here, Xcode is rarely broken. More often, teams apply Debug habits to Release, treat signing as a local-GUI-only concern, or merge export and archive into one opaque step.

Actionable next steps: pass local Archive first → add four-segment CI timing → dedicated keychain + persistent cache → then add machines or upgrade specs. For Apple platform teams, a self-hosted cloud Mac runner often saves more total time than endlessly tuning hosted-runner cache — because Archive needs a stable hot path, not a one-shot fastest chip.

Run the full Archive pipeline on cloud Mac

Dedicated M4 bare metal for xcodebuild archive, a dedicated signing keychain, and persistent DerivedData. Daily rent to benchmark Archive time and stability; upgrade to monthly when you want a permanent runner.

View cloud Mac plans · See M4 specs · Onboarding checklist