Vibeview
Pricing

App Signing & Production Builds

Build a signed, distributable release of your app in the cloud — the kind you submit to the App Store or Play Store, or hand to a tester outside VibeView — from the same pipeline you already use for cloud builds.

A production build is not something you run in VibeView. Cloud builds normally produce a simulator or emulator build that installs onto a cloud device for testing. A production build is different: it produces a signed release artifact (an .ipa or .aab/.apk) meant for a real device or a store, so VibeView cannot install or run it. It does not appear in an app’s Build History, and you can’t start a session against it. Instead it shows up on the Builds page with a Download action — you build it here, then take the file wherever it needs to go next. For the App Store and Play Store, vibeview submit can take it there for you — see Store Submission.

Production builds are available for iOS, tvOS, Android, and Android TV.

Before you start: signing credentials

A production build needs a signing credential first. There are two ways to get one into VibeView:

  • Upload existing material — an Apple certificate and profile you exported yourself, or an Android keystore you already have.
  • Generate it — VibeView creates an Android keystore for you, or, once you’ve connected your Apple account (see below), creates the iOS/tvOS certificate and provisioning profile through Apple’s API.

iOS needs two files:

  • a distribution certificate (.p12)
  • a provisioning profile (.mobileprovision) that matches it

Android needs a keystore (.jks), plus its key alias and both the keystore and key passwords.

Android TV needs no credential of its own: an Android TV production build signs with your Android credential — one keystore covers both your phone and TV builds, so there is nothing extra to upload or rotate.

tvOS uses the same Apple Distribution certificate as iOS, but provisioning profiles are platform-specific — a tvOS profile is not the same file as an iOS one. So a tvOS credential is uploaded separately: the same .p12, paired with your tvOS provisioning profile.

Upload from the CLI:

vibeview credentials add --platform ios --file cert.p12 --profile app.mobileprovision [--app <app-id>]
vibeview credentials add --platform tvos --file cert.p12 --profile app-tvos.mobileprovision [--app <app-id>]
vibeview credentials add --platform android --file release.jks [--app <app-id>]
vibeview credentials list [--app <app-id>]
vibeview credentials rm <name> [--app <app-id>]

Every password is prompted for interactively with hidden input — there is no flag for passwords, so one never ends up in your shell history. Each password (keystore password, key password, .p12 password) must be at least 8 characters — shorter passwords are rejected at upload. The key alias is not a password and has no length requirement.

Don’t have a keystore? Generate one

If you’ve never created an Android keystore, VibeView can generate one for you — no Java or keytool needed on your machine:

vibeview credentials generate --platform android --name <name>

This creates a new keystore, stores it as a signing credential (so production builds pick it up immediately), saves the keystore file locally, and prints its passwords. It’s still worth keeping your own backup of both, but a generated credential is not gone if you lose them: an organization admin can re-download the file and passwords later with vibeview credentials download (see “Credentials you generated” below). The same generated keystore signs both your Android and Android TV production builds.

Re-running the command with a name that already exists refuses to replace the stored keystore unless you pass --force. Be careful with --force: replacing a keystore already used for a published app breaks its Play Store updates — Google Play only accepts updates signed with the original key.

Connect your Apple account

To have VibeView generate iOS and tvOS credentials for you, store an App Store Connect API key first. An API key lets VibeView talk to Apple’s developer API on your team’s behalf — creating certificates and provisioning profiles — without ever touching your Apple ID password, and without any two-factor prompts.

Create one in App Store Connect under Users and Access → Integrations: generate a team API key with the App Manager or Admin role, note its Key ID and your Issuer ID, and download the .p8 key file (Apple lets you download it once). Then store it:

vibeview credentials asc-key add --key-id <KEY_ID> --issuer-id <ISSUER_ID> --file AuthKey_<KEY_ID>.p8 [--name team]
vibeview credentials asc-key list
vibeview credentials asc-key rm <name>

The key belongs to your whole organization (it’s an Apple team key, not an app-level one). Most organizations need exactly one; if you store several, pick one at generation time with --asc-key <name>. Adding a key under an existing name replaces it — that’s how you rotate one. The .p8 file itself is an uploaded secret: asc-key list shows only the name, key ID, and issuer ID, and the key can never be viewed or downloaded again. Storing or removing a key requires the Admin role.

Generate iOS and tvOS credentials

With an App Store Connect key stored, one command replaces the whole manual export-a-.p12-and-download-a-profile dance:

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

VibeView creates an Apple Distribution certificate for your team (or reuses one it created earlier — see below), registers the app’s bundle id with Apple if it isn’t registered yet, creates an App Store provisioning profile for it, and stores the result as an ordinary signing credential — production builds pick it up immediately, exactly as if you had uploaded the same material yourself.

The bundle id comes from the app you pass with --app; pass --bundle-id com.your.app instead (or as an override) if you want to provision a different one. Because a provisioning profile is tied to its bundle id, a generated Apple credential is scoped to the app when you use --app. Re-generating under an existing name refuses to replace the stored credential unless you pass --force, like every other credential.

Certificates are shared and capped. Apple limits each team to about two distribution certificates, so VibeView is careful with them: when you generate credentials for a second app (or for tvOS after iOS), it re-uses the distribution certificate it already created for your team and only issues a new profile. If Apple refuses to create a certificate because your team is at the limit, the command fails with instructions: revoke an unused certificate at developer.apple.com, or upload your own .p12 with vibeview credentials add. VibeView never revokes certificates itself — that stays your decision, in Apple’s portal.

Uploaded credentials are never readable back

Once uploaded, a credential’s file, passwords, and profile cannot be viewed or downloaded again by anyone, including you. credentials list shows only metadata: name, platform, scope, identity hash, and expiry. If you lose the original file or forget a password, there’s nothing to recover — upload a new credential (with a new name, or removing the old one first) instead. The same rule covers your App Store Connect .p8 key.

Credentials you generated can be downloaded again

Credentials that VibeView generated — Android keystores from credentials generate, and generated iOS/tvOS credentials — are the one exception. An organization admin can re-download the files and passwords:

vibeview credentials download <name> [--app <app-id>] [--out <dir>]

Every download is recorded in the organization’s audit log. This only applies to credentials generated from now on; anything you generated before this feature existed stays write-only, as promised at the time — and uploaded credentials stay write-only forever.

The identity hash in the IDENTITY column says which kind of value it is, because they are not the same thing on every credential:

  • cert sha256:… — an Android keystore in PKCS#12 format (the default since JDK 9, and what keytool -genkeypair produces today). This is the signing certificate’s own fingerprint, so it matches keytool -list -v -keystore release.jks.
  • cert sha1:… — an iOS distribution certificate, matching openssl x509 -in cert.pem -noout -fingerprint -sha1.
  • keystore-file sha256:… — a legacy JKS/JCEKS keystore, which VibeView cannot open. This is a hash of the keystore file, not of the certificate inside it, so it will not match what keytool prints. Use it to tell two uploads apart, not to identify a certificate. These also show no expiry, since reading one means opening the keystore.

Rotating a credential

Re-uploading a credential under an existing name (in the same scope) replaces it — files, passwords, and profile all at once. That’s the intended way to rotate: keep the name, upload the new material, and every future production build picks it up. Finished builds are unaffected.

Managing credentials in the dashboard

Everything above can also be done on the Builds page, under the Signing tab. Any member of your organization can view it; adding and deleting credentials requires the Admin role.

The table shows each credential’s name, platform, scope, identity hash (the same value credentials list prints), and expiry. When any credential expires within 30 days, a warning banner names it, its expiry date, and which production builds will start failing after that date.

Add credential opens an upload form: choose Apple distribution (then iOS or tvOS) or Android keystore, pick the scope (org-wide or a specific app), and provide the same files and passwords the CLI asks for. Passwords must be at least 8 characters, on a single line. Re-uploading under an existing name replaces it, exactly as from the CLI.

Delete asks for confirmation first. After a delete, new production builds stop using that credential — if nothing else valid exists in the same scope, they are refused until a replacement is uploaded. Finished builds are unaffected.

Credentials are just as write-only here as in the CLI: the dashboard shows metadata only, and an uploaded file or password can never be viewed or downloaded again — not by admins, not by support. The only exception is credentials VibeView generated, which admins can re-download (see “Credentials you generated can be downloaded again” above); every download is recorded in the audit log.

Scope

A credential is either organization-wide or tied to a specific app with --app. Organization-wide credentials apply to every app’s production builds of that platform; an app-scoped credential applies only to that app. If both exist for the same app, the app-scoped one wins — even when the app-scoped one has expired. A build never falls back from an app-scoped credential to an organization-wide one: that would silently sign the app with an identity you didn’t choose for it, so the build is refused instead (see Expiry below).

App-scoped credentials only apply to builds that are linked to that app. A production build run before the app exists (the build list shows it under “No app yet”) can only use organization-wide credentials — create the app with a regular simulator build first, set its appId in vibeview.json (or pick it in the CLI’s prompt), and app-scoped credentials apply from the next build on.

One practical note per platform: an Android keystore is app-agnostic, so a single organization-wide keystore genuinely covers every Android and Android TV app. An iOS or tvOS credential also carries a provisioning profile, and profiles are tied to a bundle id — so an organization-wide Apple credential only works across multiple apps if its profile is a wildcard one. With app-specific profiles (the common case), scope each Apple credential to its app with --app.

A build signs with exactly one credential. If several exist in the same scope for the same platform, the first unexpired one in alphabetical name order is used — the same order vibeview credentials list shows them in. So if you keep both dist-2024 and dist-2025 organization-wide and both are valid, builds sign with dist-2024; once dist-2024’s certificate expires, builds switch to dist-2025 automatically. To control which one is used, either keep a single credential per platform per scope, or scope the one you want to the app with --app.

Expiry

Signing credentials expire. An Apple credential carries two expiry dates: the distribution certificate’s and the provisioning profile’s. Both are shown, and expiry warnings and refusals track whichever comes first. Profiles typically expire sooner than certificates (profiles usually last a year at most, certificates around three), so the profile date is often the one that matters — when it passes, renew the profile and re-upload the credential under the same name, even if the certificate is still valid.

vibeview credentials list warns on any credential expiring within 30 days. An expired credential doesn’t stop your builds as long as another valid one exists in the same scope — builds skip past it to the next valid credential, as described above. Only when every credential in the scope that applies to the build has expired does VibeView refuse the build immediately — the error names the refused credential and the date it expired (the earlier of its certificate and profile dates) — rather than letting the build run and fail partway through. This includes the case where an app’s own scoped credentials have all expired while a valid organization-wide one exists: the build is still refused, because the organization-wide credential is not the identity you chose for that app. Upload a replacement in the same scope, or remove the expired app-scoped credential so the organization-wide one applies.

Previewing what a build will sign with

You don’t have to run a build to find out how these rules resolve. Open an app in the dashboard and go to its Build Settings tab: the Production signing card shows, for each platform, exactly which credential a production build of that app would use right now — with its scope and expiry state. Android and Android TV appear as a single row when they resolve to the same keystore, which is the usual case. A platform with nothing to sign with shows “No credential — production builds will be refused”, with a link to add one.

Starting a production build

vibeview build --cloud --production

The first time you run this for a platform, the CLI asks for the release build command and where the finished artifact lands (for example, a Gradle task that runs bundleRelease, producing an .aab), and saves the answer to your project config so later production builds don’t ask again.

Getting your artifact

vibeview builds download <build-id> [--out <dir>]

Or download it from the Builds page in the dashboard. A successful production build’s artifact is kept until you delete it — the same retention as any other build.

Only successful builds produce a downloadable artifact. If a production build fails or times out, nothing is kept from it — the build stays in your history with its logs, but there is no file to download. Fix the problem and run the build again.

Deleting an artifact

Once you no longer need a production build’s file, you can delete it without losing the build’s history:

vibeview builds delete-artifact <build-id>

Or use the Delete action next to Download on the Builds page — you’ll be asked to confirm, since this can’t be undone. Deleting an artifact removes the downloadable file only; the build still appears in your build history with its logs and cost, so your records stay intact. If you need the file again, run a new production build — a deleted artifact can’t be regenerated.

Android: signing config

To point your Android project at the keystore VibeView provides during a production build, add a signingConfigs.release block to android/app/build.gradle that reads these four properties:

if (project.hasProperty('VIBEVIEW_STORE_FILE')) {
    signingConfigs {
        release {
            storeFile file(VIBEVIEW_STORE_FILE)
            storePassword VIBEVIEW_STORE_PASSWORD
            keyAlias VIBEVIEW_KEY_ALIAS
            keyPassword VIBEVIEW_KEY_PASSWORD
        }
    }
    buildTypes.release.signingConfig signingConfigs.release
}

The hasProperty guard matters: these four properties are only set during a VibeView production build. Without the guard, an ordinary local build (like assembleDebug) fails immediately with MissingPropertyException before any task even runs, because Groovy resolves bare property references at configuration time. With the guard, the whole block is inert when the properties are absent, so local builds are unaffected.

Keep the signingConfig assignment inside the guard, as shown. If you wire buildTypes.release.signingConfig to signingConfigs.release unconditionally, then whenever the properties are absent the release build is pointed at a signing config with no keystore in it — which produces an unsigned release build rather than an error. VibeView refuses such an artifact when it supplied signing credentials for the build, so you get a clear failure instead of a bundle that looks signed but isn’t; keeping the assignment inside the guard avoids the situation altogether.

VibeView supplies these four values automatically during a production build — nothing else to configure.

Android key recovery

What happens if you lose an Android keystore depends on how your app is set up on Google Play:

  • With Play App Signing (the default for new apps), the keystore you hold is an upload key — Google holds the actual app signing key and re-signs your app with it. If you lose or compromise your upload key, Google Play support can help you reset it.
  • With legacy direct signing (older apps that opted out of Play App Signing), the keystore is your app signing key. If you lose it, that listing can never be updated again — there is no recovery.

Check which one applies to your app in the Google Play Console before you decide how carefully to back up your own copy of the keystore — VibeView doesn’t keep one for you either way.

iOS: signing config

Nothing to add to your Xcode project. VibeView reads the distribution type (App Store, Ad Hoc, in-house, or Development) directly from your uploaded provisioning profile, so a production build is signed and exported to match whatever kind of profile you uploaded.

What your build command must reference

VibeView provides four values to your iOS production build, all derived from the credential and profile you uploaded. Both steps of your build command need them — and leaving either set out fails the build late, after the archive has already been built and paid for.

The archive step needs to be told to sign manually, with your team, profile and certificate:

xcodebuild archive \
  -workspace ios/MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath ios/build/MyApp.xcarchive \
  CODE_SIGN_STYLE=Manual \
  ${VIBEVIEW_DEVELOPMENT_TEAM:+"DEVELOPMENT_TEAM=$VIBEVIEW_DEVELOPMENT_TEAM"} \
  ${VIBEVIEW_PROVISIONING_PROFILE:+"PROVISIONING_PROFILE_SPECIFIER=$VIBEVIEW_PROVISIONING_PROFILE"} \
  ${VIBEVIEW_CODE_SIGN_IDENTITY:+"CODE_SIGN_IDENTITY=$VIBEVIEW_CODE_SIGN_IDENTITY"}

Without these, Xcode falls back to automatic signing, which needs a signed-in Apple ID that a build machine doesn’t have — so the archive fails with a message about a missing signing certificate or development team, even though your certificate uploaded fine.

If your project uses CocoaPods (most React Native apps)

A build setting passed on the xcodebuild command line applies to every target in the workspace, including every Pod. Pod library targets cannot take a provisioning profile, so passing PROVISIONING_PROFILE_SPECIFIER this way fails the archive:

error: React-featureflags does not support provisioning profiles, but
provisioning profile MyApp Appstore has been manually specified.

Per-target signing belongs in your Xcode project, not on the command line. If your app target already has CODE_SIGN_STYLE = Manual, a DEVELOPMENT_TEAM and a PROVISIONING_PROFILE_SPECIFIER in project.pbxproj — which is the usual setup for a project that ships to the App Store — then pass none of these on the archive command. Your project’s own settings are already correct, and the only thing the archive step needs from VibeView is nothing at all:

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"}

CODE_SIGN_IDENTITY is safe to pass workspace-wide (Pods accept an identity; only a profile is rejected) and is worth passing: a project that names the legacy iPhone Distribution identity resolves it against a developer’s keychain but not necessarily against the clean keychain a build host creates from your uploaded .p12.

The export step still needs $VIBEVIEW_EXPORT_OPTIONS_PLIST either way.

Note the quotes inside ${VAR:+"..."} in every example. Without them a value containing spaces — and Apple Distribution: Your Company (TEAMID) does — is split into separate arguments, and xcodebuild reports something unrelated like Unknown build action 'Distribution'.

The export step needs the export options file VibeView writes for you, matched to the profile you uploaded:

xcodebuild -exportArchive \
  -archivePath ios/build/MyApp.xcarchive \
  -exportPath ios/build \
  -exportOptionsPlist "$VIBEVIEW_EXPORT_OPTIONS_PLIST"

You never write that file yourself — VibeView generates it from your provisioning profile at build time, including the mapping from your app’s bundle identifier to the profile that covers it.

When VibeView prompts you for a production build command and can detect your Xcode workspace, the command it offers already includes all of the above. If you write your own command, add them yourself.

The ${VAR:+"..."} form is deliberate: if VibeView can’t determine one of these values from your profile, that setting drops out of the command entirely and your project’s own setting stands, rather than being overridden with a blank.

Exporting your certificate

Export the .p12 from Keychain Access on a Mac, or from Xcode — both produce a file iOS builds can read.

If you generate the .p12 yourself with a recent version of the openssl command-line tool, add the -legacy flag:

openssl pkcs12 -export -legacy -out cert.p12 -inkey key.pem -in cert.pem

Recent OpenSSL versions default to an encryption format that macOS cannot open. Without -legacy the build fails while reading the certificate, and the underlying error reports a password problem even when your password is correct — so if you are certain the password is right, re-export with -legacy before assuming anything else is wrong.