Skip to main content

dash-ota vs the alternatives

Every tool on this page can sign bundles and verify the signature on the device. Stallion, hot-updater, CodePush and Expo's expo-updates all offer it, and in each of them the private key stays with you rather than on the update server. In all four, signing is opt-in.

What dash-ota does differently​

  • Signing is mandatory. There is no unsigned mode: the CLI signs every release with Ed25519, and native code on the device rejects any release that doesn't verify against the public keys compiled into the app. There is no setting to forget.
  • The update server never holds the signing key. The backend stores and serves releases that were signed before they reached it. Someone with root on it can hold back updates or re-serve old ones you signed, but can't create a new one.
  • Delivery is per file and content-addressed, meaning each file is stored under the SHA-256 hash of its contents. A device downloads only the files it doesn't already have, and checks each one against the hash in the signed manifest.
  • Requests are signed with a device key. Each install creates its own key pair (Android Keystore, or the Secure Enclave on iOS) and signs every request with a timestamp and a random nonce, a value the server refuses to accept twice.
  • Auto-pause is on by default. Devices report when a release fails on them; once at least 5 reports are in and 20% or more of them are failures, the server stops offering that release.
  • You host all of it. The client, CLI and backend are MIT-licensed, and no vendor service sits between your CI and your users' devices.

Two limits to know before you rely on those points. A device key proves that requests come from the same install; it doesn't prove the install is a genuine copy of your app. Enrollment accepts whatever your backend's verifyEnrollToken hook accepts, attestation is an optional hook that is off by default, and on iOS the key falls back to software unless you set OTA_REQUIRE_HARDWARE_KEY. And the AES-256-GCM payload encryption only hides blobs from someone who can read your storage and nothing else: the backend and every enrolled install can decrypt them.

See the security model and the breach walkthrough for what a compromised server can still do.

Feature matrix​

Feature matrixLast verified · September 2026
Capability
dash-ota
Stallion
hot-updater
CodePush
expo-updates
Self-hosted backend
Yes: You run it: Express middleware or a standalone server
Partial: Managed service; on-prem is plan-gated
Yes: Your own storage and database (Supabase, Cloudflare, AWS, Firebase and others)
Partial: Only as the standalone code-push-server; App Center retired on 31 Mar 2025 and the server repo was archived in May 2025
Partial: EAS-hosted; you can proxy requests through your own server or implement the open Expo Updates protocol
Open source
Yes: MIT, all four packages
Partial: SDK (MIT) and CLI are open source; the service is proprietary
Yes: MIT
Yes: MIT; the standalone server repo is archived
Yes: expo-updates is MIT and the protocol is open; EAS is a paid service
Signed bundles, verified on device
Yes: Ed25519-signed manifest, checked in native against keys compiled into the binary
Yes: SHA256withRSA, verified by the SDK on device
Yes: RSA-SHA256 since v0.23.0 (Nov 2025)
Yes: Code signing since CLI 2.1.0 (--privateKeyPath)
Yes: X.509 code signing; on EAS it needs a Production or Enterprise plan
Signing mandatory (no unsigned mode)
Yes: Every release is signed by the CLI and verified in native; the backend never has the key
Partial: Opt-in; the CLI signs locally and uploads the bundle and its signature
Partial: Opt-in; once a public key is configured, unsigned bundles are rejected
Partial: Opt-in; the CLI signs with your private key
Partial: Opt-in; the private key stays with you
Payload encryption (AES-256-GCM)
Yes: Per file. Hides blobs from anyone who can read only your storage; the backend and enrolled devices can decrypt
No
No
No
No
Device-key request auth
Yes: Per-install key in Android Keystore or the Secure Enclave; iOS falls back to a software key unless OTA_REQUIRE_HARDWARE_KEY is set
No
No
No
No
Anti-replay (nonce + timestamp)
Yes: Signed requests carry a timestamp and a random client nonce; the server refuses a nonce it has seen
No
No
No: Update checks send only the deployment key, app version and package hash
No: The Expo Updates protocol defines no request nonce
No bucket URL on the client
Yes: Blobs stream through your API with a download token scoped to one release; no CDN in the path
No
No: Client fetches a signed URL
No
No
Native-compatibility gate
Yes: runtimeVersion, enforced on the backend and in native
Partial: Target app versions
Yes: Fingerprint strategy, embedded in the binary
Partial: targetBinaryVersion (semver range)
Yes: Runtime version
Channels (dev / uat / prod)
Yes: Per-channel signing key + channel
Yes
Yes
Yes: Deployments
Yes: Branches and channels
Switch channel at runtime (QA)
No: The channel is compiled into the binary
Yes: In-app testing modal
Yes: Runtime channel switch
Yes: deploymentKey option on sync() and checkForUpdate()
Yes: "Channel surfing"
Staged rollout %
Yes: Deterministic install-id bucketing
Yes
Yes
Yes
Yes: Per-update or per-branch
Binary patches for a changed bundle
No: Unchanged files are not downloaded again, but a changed JS bundle downloads whole
Yes: "Up to 98% smaller"; Pro plan and up
Yes: e.g. a 10 MB archive becomes a ~600 KB patch
No: Only files that changed are downloaded
Yes: Bundle diffing, on by default from SDK 56
Automatic rollback on a failed boot
Yes: Two charged launches without markHealthy() disable the bundle; the device reverts to last-known-good, then the embedded bundle
Yes: Detects crashes and reverts automatically
Yes: Recovers to a working bundle if startup fails
Yes: Automatic rollback in the SDK
Yes: Error recovery returns to the last update that launched successfully and does not launch the failed one again
Server-side auto-pause on failure rate
Yes: On by default: pauses once 20% of at least 5 device reports are failures
Partial: Manual pause in the dashboard stops new downloads
Partial: Manual rollback and a force-update flag in the console
Partial: Manual, with code-push-standalone rollback or patch; no dashboard since App Center retired
Partial: Manual, with eas update:rollback
Force-update to the app store
Yes: Server sends soft or hard severity from a minimum native build; your app renders the prompt
Partial: Not documented as a store gate
Partial: Force-applies a JS update; not a store gate
No
Partial: Build it yourself
Hosted service with a free tier
No: Self-host only
Yes: Free up to 10K MAU
No: Self-hosted on your own providers
No: App Center retired on 31 Mar 2025
Yes: Free up to 1K MAU
Release CLI
Yes: @dash-ota/cli (command: dash-ota)
Yes: stallion-cli (stallion publish-bundle)
Yes: npx hot-updater
Yes: code-push-standalone; the old appcenter codepush CLI no longer works
Yes: EAS CLI
New Arch + Hermes (RN 0.79+)
Yes
Yes
Yes
Partial: Community forks only
Yes
License / ownership
MIT, self-hosted
MIT SDK and CLI, proprietary service
MIT
MIT; App Center retired 31 Mar 2025
MIT client; paid EAS service with a free tier
First-class Partial / manual— hover for detail Not available

Competitor cells were checked against each project's own documentation in September 2026. Hover a cell for its note. If something here is out of date or unfair, open an issue.

Where the others are ahead​

  • Patches. hot-updater, Stallion (Pro plan and up) and EAS Update (bundle diffing, on by default from SDK 56) send a diff against the bundle the device already has. hot-updater's docs give the example of a 10 MB archive shipping as a patch of about 600 KB. dash-ota doesn't generate patches: unchanged files aren't downloaded again, but a changed JS bundle downloads whole.
  • CDN delivery. hot-updater gives the device a signed URL to your storage. dash-ota streams every blob through your own API, so your API carries all of the download traffic.
  • Switching channels for QA. EAS Update's "channel surfing", hot-updater's runtime channel switch, Stallion's in-app testing modal and CodePush's deploymentKey option all let a tester point one build at another channel. In dash-ota the channel is compiled into the binary, so testing another channel means installing another build.
  • Hosted free tiers. Stallion is free up to 10K monthly active users and EAS Update up to 1K. dash-ota has no hosted option; you run the backend.
  • The Expo workflow. EAS Update can publish an update from a pull request and preview it in a development build. If your app is built with Expo and EAS, expo-updates fits that toolchain far better.
  • Crash tooling. hot-updater has withSentry(), which uploads source maps. dash-ota has nothing built in; you upload source maps to your crash reporter yourself.
  • Adoption. npm downloads for the last month: expo-updates about 13.9 million, @hot-updater/react-native about 131k, react-native-stallion about 28k, react-native-dash-ota about 460. More users means more bugs already found and more answers already written.
  • Older React Native. Stallion supports React Native 0.69 and later. dash-ota needs 0.79 or later with the New Architecture.

The roadmap lists which of these dash-ota plans to close.

When not to choose dash-ota​

  • You want a managed dashboard, analytics and support, and are comfortable with a vendor: Stallion is less setup, has a free tier up to 10K MAU, and can sign its bundles too.
  • You build with Expo and EAS: expo-updates has X.509 code signing (on EAS it needs a Production or Enterprise plan) and is built into that toolchain.
  • You want the fastest path to a self-hosted setup: hot-updater works with Supabase, Cloudflare, AWS and Firebase, has a web console and bundle signing, and ships binary patches. dash-ota asks you to set up signing keys before your first publish.
  • Your app runs React Native older than 0.79 or the old architecture.

dash-ota fits teams that must be able to say what happens when the update server is compromised, and who will run a backend and manage signing keys to get there. If that isn't a requirement you have, one of the others will get you shipping faster.

Migrating?​