NewiPhone Duo, live in your browser. Try it →
Vibeview
Pricing
Cloud Builds · Capacitor

Build an Ionic or Capacitor iOS app without a Mac

Rokas Kašinskas — Co-founder, VibeViewPublished 2026-10-16Updated 2026-10-16

A Capacitor app is a web app inside two native projects, so one build is three jobs: build the web app, copy it into ios/ and android/ with cap sync, then compile each native project with its own toolchain. That last step is the one that ties you to a Mac for iOS and to Android Studio for Android. VibeView cloud builds run all three for you. The CLI uploads your project source, the build runs in a clean environment, and the finished build lands next to your app, ready to run on a device you open in a browser tab.

This guide takes one Capacitor app from source to running builds on both platforms, then to a signed Android release. It works the same for Ionic apps that use Capacitor, and from Windows, Linux or macOS.

What you need

  • A VibeView account and the CLI: npm install -g vibeview && vibeview login. CLI 0.42.0 or later suggests the Capacitor commands; on an older CLI, type them in when it asks.
  • A Capacitor project with its ios/ and android/ folders committed (the ones npx cap add ios and npx cap add android create). The build syncs your web app into them but doesn’t create them.

You don’t need Xcode, CocoaPods, the Android SDK or a JDK locally. Every plan includes cloud builds each month; builds beyond your plan’s allowance draw from usage credit. The pricing page has each plan’s numbers.

Part 1: debug builds on cloud devices

  1. Start the builds from your project root.

    vibeview build --cloud --platform all

    The first run asks for each platform’s build command and where the result lands, and saves the answers to vibeview.json. For a Capacitor 8 project with a package-lock.json the CLI suggests:

    {
      "defaultPlatform": "android",
      "platforms": {
        "android": {
          "build": {
            "command": "npm ci && npm run build && npx cap sync android && cd android && JAVA_HOME=${JAVA_HOME_21:-$JAVA_HOME} ./gradlew assembleDebug",
            "artifact": "android/app/build/outputs/apk/debug/*.apk"
          }
        },
        "ios": {
          "build": {
            "command": "npm ci && npm run build && npx cap sync ios && xcodebuild -project ios/App/App.xcodeproj -scheme App -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build CODE_SIGNING_ALLOWED=NO",
            "artifact": "ios/build/Build/Products/Debug-iphonesimulator/*.app"
          }
        }
      }
    }

    The install step follows your lockfile (npm ci, yarn install --immutable or pnpm install --frozen-lockfile), because node_modules is never uploaded. The web build runs your build script, whatever framework it uses, and is left out when package.json has none.

  2. Watch the logs. The CLI packs every file git tracks plus untracked files that .gitignore doesn’t exclude, uploads them, and streams each build’s output. Add --no-wait to start the builds and leave; vibeview builds logs <id> --follow picks one back up.

  3. Run them. A succeeded 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 (name, bundle id and icon included) 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 or Android emulator.

You’ll know it worked when the build page shows added to app builds on its timeline and your web app opens inside the native shell on the device. The web assets are bundled into the build, so it runs on its own: share it as a link, record tests on it, or let your coding agent tap through it.

Why the Android command sets JAVA_HOME

Capacitor 7 and later compile their Android library for Java 21, and they ask for it directly rather than through a Gradle toolchain. On the default Java 17 the build stops with invalid source release: 21. The cloud builders have both Java versions and set JAVA_HOME_21, so the suggested command runs Gradle on Java 21. The :-$JAVA_HOME part leaves a local vibeview build on your machine’s own Java. The CLI adds the part when your Gradle wrapper is 8.5 or later, as Capacitor 7 and later ship; Capacitor 6’s older Gradle can’t run on Java 21, so its suggestion leaves the part out. After a Capacitor upgrade, add it to the saved command in vibeview.json yourself, or the build fails with the same error and says which line to add.

Older projects that use CocoaPods

Capacitor 8 projects use Swift Package Manager, so the iOS command builds ios/App/App.xcodeproj directly. A project with a Podfile in ios/App, the CocoaPods setup of earlier Capacitor versions, builds the workspace instead: the CLI sees the Podfile and suggests -workspace ios/App/App.xcworkspace. npx cap sync ios runs pod install, and the cloud builders have CocoaPods.

Part 2: a signed .aab for Google Play

The release is a signed .aab for the store, not a build that runs on a VibeView device, so it lands on the Builds page with a Download action. The Capacitor-specific part is that the same install, web build and sync run first.

  1. Get a keystore. Upload the one you have with vibeview credentials add --platform android --file release.jks, or let VibeView make one with vibeview credentials generate --platform android --name release.

  2. Point Gradle at it. Add the signing block from App Signing to android/app/build.gradle. VibeView sets the four properties it reads only during a production build, so your local builds keep working as before.

  3. Build the release.

    vibeview build --cloud --production --platform android

    The first run offers the same install, web build and sync, then ./gradlew bundleRelease, with the artifact at android/app/build/outputs/bundle/release/*.aab. Then vibeview submit --platform android --latest --track internal uploads it to Google Play; the Android cloud build guide covers the one-time Play Console setup.

For iOS, a production build is your own archive command signed with your Apple credentials: see App Signing.

Troubleshooting

  • invalid source release: 21. Gradle ran on Java 17. Run it with JAVA_HOME=${JAVA_HOME_21:-$JAVA_HOME} as in the suggested command.
  • Cannot find module or a missing node_modules error. The build command didn’t install dependencies. Add your install step to the front of it.
  • The app opens on a blank or old screen. The command skipped the web build or cap sync, so the native project carries whatever web assets were last committed. Keep both legs in the command.
  • The CLI suggests no iOS command. It couldn’t find ios/App/App.xcodeproj with one scheme. Commit the ios/ folder, or enter your own xcodebuild command.

Frequently asked questions

Frequently asked questions

Does this work for Ionic apps?

Yes, if it uses Capacitor, which new Ionic apps do. The CLI finds the capacitor.config file and suggests the same commands, with your Ionic build as the web build step. An older Ionic app on Cordova isn’t a Capacitor project, so write its build command yourself.

Can I build a Capacitor iOS app without a Mac?

Yes. The iOS cloud build runs on a macOS builder with Xcode and CocoaPods, and the simulator build it makes opens on a cloud iPhone in your browser. You still need an Apple Developer account to sign and publish a production build.

Can I build for Apple TV or Android TV?

Capacitor doesn’t support Apple TV. The CLI doesn’t suggest an Android TV development build command, so enter your own when it asks.

Next steps

Try it yourself

Open a live iOS simulator or Android emulator in your browser.

Start free