Migrate from Stallion
Stallion is a managed OTA service with an open-source SDK and CLI. Moving to dash-ota means you run the backend and the release tooling yourself. Stallion already offers bundle signing as an opt-in; in dash-ota it's mandatory. The client APIs differ, so plan to rewrite the code that reads update state rather than rename it.
Concept mapping
| Stallion | dash-ota |
|---|---|
withStallion(App) | <DashOtaProvider config={…}> wrapping your app |
useStallionUpdate() | useOtaUpdate() |
isRestartRequired | status === 'apply-pending' |
currentlyRunningBundle | currentBundle |
newReleaseBundle | availableUpdate |
restart() | applyUpdate(true) |
| no equivalent | markHealthy(), which ends the new bundle's trial (see below) |
| Stallion dashboard / cloud | your self-hosted backend and the local dashboard |
stallion publish-bundle (npm stallion-cli) | dash-ota publish (npm @dash-ota/cli) |
StallionProjectId, StallionAppToken, StallionPublicSigningKey in strings.xml / Info.plist | Android string resources ota_channel, ota_server_url, ota_public_keys, ota_runtime_version and integer ota_native_build; iOS Info.plist keys OTA_CHANNEL, OTA_SERVER_URL, OTA_PUBLIC_KEYS, OTA_RUNTIME_VERSION |
native getJSBundleFile / bundleURL override | DashOtaBundleLoader.getBundleFile(applicationContext) / DashOtaBundleLoader.bundleURL() |
Steps
- Stand up the backend. Mount
dashOtaMiddlewarein an Express app, or run it standalone. This replaces the Stallion cloud. - Generate signing keys per environment (keygen) and
embed the public key in each flavour. If you used Stallion's bundle signing, this key
replaces
StallionPublicSigningKey. dash-ota has no unsigned mode, so it won't publish without a key. - Swap the provider. Replace
withStallionanduseStallionUpdatewith<DashOtaProvider>anduseOtaUpdate(), using the mapping above. - Add a ready signal. Stallion has none, so this is new code. A new bundle runs on trial
until your app calls
markHealthy(); call it once the first real screen works. Nothing calls it for you unless you setautoMarkHealthyMs. See the crash-loop breaker. - Swap the native hooks. Point Android's bundle file and iOS's
bundleURL()atDashOtaBundleLoaderinstead of Stallion's loader. The exact edit depends on your React Native template; see Android setup and iOS setup. - Replace publish scripts. Swap
stallion publish-bundlefordash-ota publish, which needs--bundle-dirand--app-idand encrypts and signs the release. - Remove Stallion. Delete the
Stallion*entries fromstrings.xmlandInfo.plist, and the Stallion dependency.
What changes
- Signing can't be switched off, and every release is verified in native code before it's written to disk.
- Requests are signed with a per-install device key, with a timestamp and a nonce the server won't accept twice.
- Blobs are encrypted with AES-256-GCM by default and stream through your API; the client never sees a storage URL.
- The server pauses a release on its own when devices report failures. In Stallion, pausing is a manual action in the dashboard.
- There's no vendor in the path between your CI and your users' devices.
What you give up
- A hosted service, with a free tier up to 10K monthly active users, and vendor support.
- Patches: Stallion's Pro plan and up ship diffs that are much smaller than a full bundle. dash-ota skips unchanged files, but a changed JS bundle downloads whole.
- The in-app testing modal. In dash-ota the channel is compiled into the binary, so testing another channel means installing another build.
- React Native versions below 0.79. Stallion supports 0.69 and later.
What to plan for
- You now run a backend (small, but yours). See deployment.
- You manage signing keys (custody and rotation).