Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Submit to TestFlight from a Rented Remote Mac

Direct answer: to submit an iOS build to TestFlight without a local Mac, rent one and drive the Xcode toolchain through the browser console. The Figment virtual Intel macOS at $29/month is enough to archive, sign and upload an iOS IPA on the Xcode 26 line; move up to an M-series Figment Mac from $95/month (exact chip, RAM and disk confirmed in the quote) if you need Xcode 27, Apple-silicon-only frameworks, visionOS, or heavier parallel builds. Start a session at /mac-hosting/start; the desktop terminal inside the session runs every command below. The commands have not been executed end-to-end in this guide — run each against your own project and check its exit status.

What a TestFlight upload actually needs

Before any script runs, assemble these pieces on the rented Mac:

  • Apple Developer Program membership. Free accounts cannot upload to TestFlight. The membership and the device signing you perform on Figment hardware are separate from the hosting plan.
  • A distribution signing identity. The certificate is useless without its private key. Export them together on your original Mac as a passphrase-protected .p12, transfer that .p12 to the rented Mac, import once with security import ... -k ~/Library/Keychains/login.keychain-db, then delete the .p12 from disk. The identity now lives inside the keychain, not in the file.
  • A matching provisioning profile file. Profiles are files, not keychain items. Install them under ~/Library/MobileDevice/Provisioning Profiles/<UUID>.mobileprovision.
  • An App Store Connect app record with your final bundle ID registered under the correct team.
  • An App Store Connect API key (.p8 plus key ID and issuer ID). Both Apple's direct upload flow and Fastlane pilot accept this key.

External beta testing adds App Review. Per Apple's TestFlight page, you must supply a beta app description, beta app review information, and a monitored feedback email; your first external build is sent to App Review automatically, and external testers can only be invited after that build is approved.

Pick the right Figment Mac for the archive

Apple's porting guide tells Intel users to run Xcode 26 or earlier; Xcode 27 requires Apple silicon. Intel Xcode 26 can still cross-compile arm64 device apps and universal macOS binaries, so an Intel host remains a working TestFlight submitter for the Xcode 26 line.

NeedFigment planList priceNotes
Submit an iOS build on the Xcode 26 lineVirtual Intel macOS$29 / month2 vCPU, 8 GiB RAM, 80 GiB disk. Browser-console access. No GPU acceleration; no audio.
Larger Intel physical build hosti3 Mac$49 / monthRequest-based while supply is confirmed.
Xcode 27, visionOS, Apple-silicon-only tooling, heavier concurrencyM-series Macfrom $95 / monthExact chip, RAM and disk confirmed in the quote; request-based.
Managed CI, backups, monitoring, support minutesManaged Build Mac / Mac Team$249 or $999 / monthOne or two M-series Macs with CI setup included.

Access on the virtual Intel VM is the browser desktop plus its terminal; inbound public SSH is not implemented on the $29 plan. Outbound CI agents (GitHub Actions self-hosted runner, Buildkite, Xcode Cloud triggers) can still connect out to their orchestrators from inside the session.

Archive, export and upload with Fastlane pilot

Apple's Distributing your app for beta testing and releases documents the archive → distribute → upload path the Xcode Organizer uses. From a headless terminal the same endpoint is driven more cleanly through Fastlane's pilot (alias upload_to_testflight), whose api_key_path and ipa parameters are listed in the action reference.

Pre-flight checker (reader tool). Save as scripts/testflight-preflight.sh and run before any archive. It reports what is present; it does not try to fix anything:

#!/usr/bin/env bash
set -euo pipefail

printf '%-26s %s\n' 'xcodebuild:'          "$(xcodebuild -version | head -n1)"
printf '%-26s %s\n' 'fastlane:'            "$(bundle exec fastlane --version 2>/dev/null | tail -n1)"
printf '%-26s %d\n' 'profiles installed:'  \
  "$(ls -1 ~/Library/MobileDevice/Provisioning\ Profiles 2>/dev/null | wc -l)"
printf '%-26s %d\n' 'distribution certs:'  \
  "$(security find-identity -v -p codesigning | grep -c 'Apple Distribution')"
printf '%-26s %s\n' 'ASC key JSON present:' \
  "$([ -r "${ASC_KEY_JSON:-config/asc_api_key.json}" ] && echo yes || echo no)"
printf '%-26s %s\n' 'scheme env var:'      "${MY_SCHEME:-UNSET}"

Zero on any count means fix the prerequisite before running the real build. Local xcodebuild -exportArchive -help on Xcode 16.4 lists the current method values: app-store-connect, release-testing, enterprise, debugging, developer-id, mac-application, validation. The old app-store, ad-hoc and development names are deprecated aliases — confirm with the installed binary on your rented Mac before you hardcode one.

#!/usr/bin/env bash
set -euo pipefail

: "${MY_SCHEME:?set MY_SCHEME to your release scheme}"
: "${TEAM_ID:?set TEAM_ID to your 10-character Apple team id}"

xcodebuild archive \
  -workspace   MyApp.xcworkspace \
  -scheme      "$MY_SCHEME" \
  -destination "generic/platform=iOS" \
  -archivePath "$HOME/Archives/MyApp.xcarchive" \
  | tee archive.log
test "${PIPESTATUS[0]}" -eq 0 || { echo "archive failed"; exit 1; }

cat > ExportOptions.plist <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>method</key>        <string>app-store-connect</string>
  <key>teamID</key>        <string>${TEAM_ID}</string>
  <key>uploadSymbols</key> <true/>
</dict></plist>
PLIST

xcodebuild -exportArchive \
  -archivePath        "$HOME/Archives/MyApp.xcarchive" \
  -exportPath         "$HOME/Export/MyApp" \
  -exportOptionsPlist ExportOptions.plist

IPA=$(find "$HOME/Export/MyApp" -maxdepth 1 -name '*.ipa' | head -n1)
[ -n "$IPA" ] || { echo "no IPA produced"; exit 1; }

bundle exec fastlane pilot upload \
  --api_key_path "${ASC_KEY_JSON:-config/asc_api_key.json}" \
  --ipa "$IPA" \
  --skip_waiting_for_build_processing true

The explicit PIPESTATUS check prevents a successful tee from masking an xcodebuild failure; a failed archive is never a successful submission. pilot upload is documented in Fastlane's upload_to_testflight reference with api_key_path, ipa and skip_waiting_for_build_processing as the parameters used here. For every retry, bump CFBundleVersion first — App Store Connect rejects duplicate build numbers.

Once the upload completes, open your app in App Store Connect → TestFlight → Builds. The row advances from Processing to Ready to Submit within a few minutes. Any warning cites the exact Info.plist key, entitlement or encryption-export answer to address before the next upload.

Keep the API key off disk and out of shell history

The asc_api_key.json file holds the key id, issuer id, and the base64-encoded .p8 payload. It is a credential, so handle it like one:

  1. Store the JSON in your CI secret manager (GitHub Actions encrypted secrets, Buildkite secrets, Bitrise generic file storage). Materialize it at job start with umask 077 set before writing, and register a shell trap 'rm -f "$ASC_KEY_JSON"' EXIT INT TERM so a clean exit removes the file. A trap cannot clean up after a hard kill or machine failure; if a job dies mid-run, rotate the key in App Store Connect on suspicion rather than relying on the trap.
  2. If you must stage a long-lived copy on the rented Mac, write it with install -m 600 into a path only your user can read, and rotate the key in App Store Connect every time a teammate's access ends.
  3. Do not run env > .env on the rented Mac. That captures every unrelated token in the current shell and drops them into a file anything else on the machine can read later. Move only the specific names you need, and let their values come back from the secret manager at job time.

The signing .p12 is a separate artifact — certificate plus private key, exported with a passphrase. Carry the passphrase through the same secret manager, delete the .p12 from disk after security import succeeds, and keep the provisioning profile file beside it only until it is installed under ~/Library/MobileDevice/Provisioning Profiles/.

If you need to allowlist outbound traffic for App Store Connect, update your firewall, artifact store or CI runner egress. Apple does not publish a stable ingress range for App Store Connect, so there is nothing fixed to pin from the other side. For a persistent build pipeline, prefer a managed Figment plan ($249 or $999) that bundles backups and monitoring with the Mac, rather than rebuilding the environment on every retry.

Start a configuration at /mac-hosting/start or review plans at /mac-hosting.


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms