A typical fastlane ad hoc lane registers new devices, refreshes the provisioning profile, rebuilds and uploads. BuildFerry takes over the first three for each tester you approve, so your lane only runs when the code changes.
The usual ad hoc lane
lane :adhoc do register_devices(devices_file: "devices.txt") match(type: "adhoc", force_for_new_devices: true) build_app(export_method: "ad-hoc") # then upload the .ipa somewhere testers can reach it end
Every new tester means collecting their UDID, adding it to devices.txt and running the whole lane again.
The lane with BuildFerry
lane :testers do
build_app(export_method: "ad-hoc")
sh("npx", "--yes", "buildferry@1", "upload", lane_context[SharedValues::IPA_OUTPUT_PATH])
endSet BUILDFERRY_TOKEN to a token from Upload from CI in the BuildFerry dashboard, in your CI's secrets or your shell. The last line uploads the .ipa that build_app just made, waits while BuildFerry checks it, and prints the share link. If a build can't be shared, for example an App Store export, the lane stops with the reason.
It runs the BuildFerry CLI, so the machine needs Node.js 18 or newer. GitHub's macOS runners and most CI images already have it.
What happens when someone new joins
- They open your app's share link in Safari and allow a one-time profile. That's how BuildFerry gets the UDID.
- You approve their iPhone in the dashboard.
- BuildFerry registers it with your Apple team, updates the profile and re-signs your latest build in the cloud.
- They tap Install.
Your lane doesn't run, and devices.txt doesn't change.
Useful options
| Option | |
|---|---|
--no-wait | Don't wait for the check |
--timeout 900 | Wait longer for a very large build (seconds) |
--json | Print only the result as JSON: status, version, build, shareUrl, appUrl and testers |
On GitHub Actions you can skip fastlane for this step and use the BuildFerry action.
Questions
Do I still need fastlane match?
You still need a way to sign the export, and match is a good one. What you no longer need is to regenerate the ad hoc profile for each new device: BuildFerry re-signs the build for every iPhone you approve.
Does this need Node.js?
Yes, Node.js 18 or newer, because the step runs the BuildFerry CLI with npx. GitHub’s macOS runners and most CI images include it.
Can I keep using register_devices?
You can, but you don't have to. Devices registered either way count toward the same Apple limit of 100 per device family each membership year, and BuildFerry shows the exact count.