Black Friday30% off your first month. See prices →
VibeView

Cloud Builds · iOS

Build a React Native iOS app without a Mac, in the cloud

Rokas Kašinskas — Co-founder, VibeViewPublished 2026-08-14Updated 2026-10-25

Compiling an iOS app has always meant macOS: Xcode is Mac-only, so React Native teams without a Mac either kept a macOS CI runner alive or borrowed a laptop every time the native side changed. Then came signing: a certificate exported from Keychain Access, a provisioning profile from the developer portal, and Transporter to get the result into TestFlight. VibeView cloud builds move every one of those steps off your machine.

This guide follows one app from source to TestFlight: a simulator build on a cloud iPhone first, then a signed .ipa, then the upload. It works the same from Windows, Linux or macOS, for bare React Native and Expo projects.

What you need

  • A VibeView account and the CLI, 0.51.0 or later for build profiles: npm install -g vibeview && vibeview login
  • A React Native project, bare or Expo (for a managed Expo project with no ios/ folder, Build an Expo app without EAS covers the prebuild details)
  • For the signed build and TestFlight: an Apple Developer Program membership and an App Store Connect API key (covered below)

You don’t need Xcode, CocoaPods or Ruby locally. Every plan includes cloud build minutes each month; builds are billed by the minute, and minutes beyond your plan’s allowance draw from usage credit. The pricing page has each plan’s numbers.

Part 1: a simulator build on a cloud iPhone

  1. Start the build from your project root.

    vibeview build --cloud --platform ios

    The first run asks for two things and saves them to vibeview.json as the development profile: the build command, and where the finished .app lands. When the CLI can find your Xcode workspace, it suggests an xcodebuild command for the simulator. Make sure the command installs your dependencies first, because node_modules and ios/Pods are never uploaded. If you take the suggested command, the CLI also saves a preview profile, the same build in Release:

    {
      "defaultPlatform": "ios",
      "platforms": {
        "ios": {
          "build": {
            "profiles": {
              "development": {
                "command": "yarn install --immutable && cd ios && pod install && cd .. && xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build CODE_SIGN_IDENTITY=-",
                "artifact": "ios/build/Build/Products/Debug-iphonesimulator/*.app"
              },
              "preview": {
                "command": "yarn install --immutable && cd ios && pod install && cd .. && xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -derivedDataPath ios/build CODE_SIGN_IDENTITY=-",
                "artifact": "ios/build/Build/Products/Release-iphonesimulator/*.app"
              }
            }
          }
        }
      }
    }

    The development build loads its JavaScript from Metro, which is what vibeview dev wants. The preview build has the JavaScript inside and runs by itself, so it’s the one to share or test; build it with vibeview build --cloud --platform ios --profile preview.

    Use whatever your lockfile calls for: npm ci, yarn install --immutable (Yarn 1: yarn install --frozen-lockfile with "packageManager": "yarn@1.x" in package.json, or npx --yes yarn@1 install --frozen-lockfile without it), pnpm install --frozen-lockfile or bun install.

  2. Watch the log. The CLI packs every file git tracks plus untracked files that .gitignore doesn’t exclude, uploads them, and streams the build output. The log opens with the Xcode and iOS SDK versions your app is compiled against. Add --no-wait to start the build and leave; vibeview builds logs <id> --follow picks it back up.

  3. Run it. A succeeded simulator build lands in the app’s Build history with a cloud badge. If the project had no app yet, the build creates one from the binary and the CLI writes its appId into vibeview.json. Open the build’s page and choose Run this build to start it on a cloud iPhone in the browser, or run vibeview dev --platform ios to work on it with Metro and hot reload.

You’ll know it worked when the build page shows added to app builds on its timeline and the app launches on the simulator. From here it’s the normal VibeView loop: share the build as a link with someone who has no Mac, run your recorded tests against it, or hand the device to your coding agent. For a share link or a test run, use the preview build: it works without Metro running. Sharing without TestFlight covers the link.

Part 2: a signed .ipa for the App Store

A production build is a different artifact with a different job. It is signed with your distribution certificate, it is meant for TestFlight and the App Store, and a simulator can’t install it. It shows up on the Builds page with a Download action instead of in Build history.

  1. Store an App Store Connect API key. This is what lets VibeView create signing material and upload builds for your team, with no Apple ID password and no two-factor prompts. In App Store Connect, open Users and Access → Integrations, generate a team key with the App Manager or Admin role, note its Key ID and your Issuer ID, and download the .p8 file (Apple lets you download it once). Then:

    vibeview credentials asc-key add --key-id <KEY_ID> --issuer-id <ISSUER_ID> --file AuthKey_<KEY_ID>.p8
  2. Generate the certificate and profile. One command replaces exporting a .p12 from Keychain Access and downloading a profile from the developer portal:

    vibeview credentials generate --platform ios --name release --app <app-id>

    VibeView creates an Apple Distribution certificate for your team (or reuses the one it made earlier), registers the app’s bundle id with Apple if needed, creates an App Store provisioning profile, and stores both as a signing credential. Already have your own signing files? Upload them instead; the .p12 password is prompted with hidden input:

    vibeview credentials add --platform ios --file cert.p12 --profile app.mobileprovision --app <app-id>
  3. Set manual signing on the app target. A build machine has no signed-in Apple ID, so Xcode’s automatic signing can’t work there; the app target has to sign manually with your team and profile. Setting it on the app target is safer than passing the profile on the xcodebuild command line: in a CocoaPods project (which nearly every React Native app is) a command-line profile reaches every Pod target too, and some Pods reject it and fail the archive.

    • Expo or a prebuild workflow: a small config plugin writes manual signing onto the app target during prebuild, using the team and profile VibeView provides to the build. The Kitlist sample app has a working one in plugins/.
    • Bare React Native: set CODE_SIGN_STYLE = Manual, your DEVELOPMENT_TEAM and the PROVISIONING_PROFILE_SPECIFIER on the app target’s Release configuration in ios/MyApp.xcodeproj/project.pbxproj. That’s the usual setup for a project that already ships to the App Store; App Signing explains exactly what the archive step needs.
  4. Turn on automatic build numbers. App Store Connect refuses an upload that reuses a build number. Set autoIncrement on the production profile and every production build gets the next number from a counter VibeView keeps per app, passed to the build as VIBEVIEW_BUILD_NUMBER. Map it to your app’s build number once (how). Because an App Store Connect key is stored, the first number starts above the builds your app already has in App Store Connect.

  5. Build the release.

    vibeview build --cloud --profile production --platform ios

    The first run asks for the release command: an xcodebuild archive followed by an export. Pass only the signing identity on the archive step, and the export options file VibeView writes from your profile on the export step. The CLI saves it as the production profile, next to the other two (--production is short for --profile production):

    {
      "platforms": {
        "ios": {
          "appId": "my-app-abc123",
          "build": {
            "profiles": {
              "development": {
                "command": "...",
                "artifact": "ios/build/Build/Products/Debug-iphonesimulator/*.app"
              },
              "preview": {
                "command": "...",
                "artifact": "ios/build/Build/Products/Release-iphonesimulator/*.app"
              },
              "production": {
                "command": "yarn install --immutable && cd ios && pod install && cd .. && xcodebuild archive -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Release -archivePath ios/build/MyApp.xcarchive ${VIBEVIEW_CODE_SIGN_IDENTITY:+\"CODE_SIGN_IDENTITY=$VIBEVIEW_CODE_SIGN_IDENTITY\"} && xcodebuild -exportArchive -archivePath ios/build/MyApp.xcarchive -exportPath ios/build -exportOptionsPlist \"$VIBEVIEW_EXPORT_OPTIONS_PLIST\"",
                "artifact": "ios/build/*.ipa",
                "autoIncrement": true
              }
            }
          }
        }
      }
    }

    Keep the quotes inside ${...:+"..."}: the identity name contains spaces, and without them xcodebuild splits it into separate arguments. vibeview builds download <id> saves the .ipa locally if you want the file itself.

Part 3: upload the .ipa to TestFlight

Apple only accepts uploads for an app that already exists in App Store Connect, so create the record once: My Apps → + → New App, platform iOS, and the bundle id your build is signed with. If VibeView generated your credentials, it already registered that bundle id with Apple, so it’s in the list. Then check that Apple knows the app before you spend a build on it:

vibeview submit --check --platform ios --bundle-id com.example.myapp

And upload it, straight to a group of testers:

vibeview submit --platform ios --latest --group "QA team" --what-to-test "First TestFlight build"

--latest picks your newest succeeded production build. --group names a TestFlight group as it is called in App Store Connect. --what-to-test is the note its testers see. Both need CLI 0.55.0 or later; leave them out to upload without testers. Apple processes the build first. VibeView then answers export compliance, writes your notes to the build and puts it in your group. An external group goes through Apple’s Beta App Review before its testers get the build. If neither your Info.plist nor an earlier submission answered Apple’s encryption question, pass --no-uses-encryption (or --uses-encryption). App Store review, the store listing and screenshots stay in App Store Connect. Eligible builds also get a Submit to store button on the dashboard’s Builds page. Upload to TestFlight without a Mac goes deeper on this step.

Troubleshooting

  • The archive fails with “does not support provisioning profiles”. A profile reached the Pod targets through the command line. Move manual signing onto the app target (step 3 of Part 2) and keep only CODE_SIGN_IDENTITY on the archive command.
  • The archive fails asking for a signing certificate or development team. The app target is still on automatic signing. Set manual signing as in step 3.
  • Cannot find module or a missing Pods error early in the build. The build command didn’t install dependencies. Add the install step and pod install.
  • Apple refuses a duplicate build number. Turn on autoIncrement, or seed the counter once with vibeview versions set --app <app-id> --platform ios <number>.
  • The build succeeds but says it can’t run on VibeView devices. The command produced a device build instead of a simulator one. Check that the simulator command passes -sdk iphonesimulator.

Frequently asked questions

Do I need a Mac at any point?

No. The compile, the signing and the TestFlight upload all run on VibeView’s side; you run CLI commands from Windows, Linux or macOS. You do need an Apple Developer Program membership to sign and publish, the same as with a Mac.

Which Xcode version do cloud builds use?

Xcode 26.5, unless you add "xcode": "27.1" to the iOS build block (or pass --xcode 27.1) for the iOS 27 SDK; apps built with it must use the scene-based life cycle (UIScene) to launch on iOS 27. Beta Xcode versions aren’t offered, and App Store Connect doesn’t accept apps built with them anyway. The build log prints the Xcode and iOS SDK versions. See Which Xcode iOS builds use.

Can I run the signed .ipa on a VibeView simulator?

No. An .ipa is a device build, and simulators can’t install it. Check your change on the preview build from Part 1, then ship the production build.

Does this work for an Apple TV app?

Yes. tvOS builds use the same commands with --platform tvos and a tvOS provisioning profile. Build an Apple TV app without a Mac walks through it.

Can my coding agent run these builds?

Yes. The commands are the same, --json emits machine-readable build events, and the Builds page gives you a ready-made prompt for Claude Code, Codex or Cursor that sets up the build profiles and starts the builds. An AI agent shipped an Expo app to TestFlight in 43 minutes shows a full run.

Next steps

Try it yourself

Open a live iOS simulator, Android emulator, Apple TV, Android TV or Roku in your browser.

Start free