The complete control plane for Expo apps.
Self-hosted.

Publish, roll out, roll back, and watch every update you ship. Health, adoption, events and logs come back from every device that received it, into your servers, your storage and your ClickHouse, over the official Expo Updates protocol.

ota.acme.internal/branches/production
All branchesproduction1.0.0
Rollout in progress

Update 17844529170002 · publishing to this branch and runtime version is paused until the rollout ends.

26IncreaseFinish rolloutRevert
25%

Updates

UpdatePublished

feat(observe): Implement health history tracking a…

2group:ad83acfb-32b9-4e4a-b8e2-5399f744a43e

24 Jul 2026 at 16:54

feat(observe): Implement health history tracking a…

2group:68c97637-eaf0-4aa7-ade1-1e8ec9b64be5

24 Jul 2026 at 16:44

feat: add healthRelevant field

2group:481aa951-ef22-4715-847f-7dc2b1c4d1ab

24 Jul 2026 at 16:24

Merge branch 'main' into feature/observe

2group:4c9023e7-fc1f-4416-adfa-6e85aa35b110

24 Jul 2026 at 15:21

Update 17848984081811

Rollback to embedded

24 Jul 2026 at 15:06

Update 17848984081702

Rollback to embedded

24 Jul 2026 at 15:06

Update 17848897610932

2group:0fff5e25-fdc3-440c-93e5-617580920fb7

24 Jul 2026 at 12:42

Update 17849048644461

Published 24 Jul 2026 at 16:54:24

iOSNormal update

Health over time

Near-real-time health, retained in one-minute buckets.

24/07/2026, 16:45:00 → 25/07/2026, 14:30:00

Synced 14:43:29

HealthAdoptionFaults

Successful devices across all attempts

iOS 95.1%

100%50%0%
18:0000:0006:0012:00

Deployment

Branch
production
Runtime version
3.0.0
Platform
iOS
Published
24 Jul 2026 at 16:54:24

Manage your OTA updates.

xprem implements the official Expo Updates protocol, so your app keeps the standard expo-updates runtime. Publish, roll back and roll out progressively, with every bundle in your own bucket and every download URL pointing at infrastructure you own.

Explore Updates →
acme-app · zshota.acme.internal

~/acme-app $ npx eoas publish --branch production

Exporting bundleios · android · hermes0.9s

Uploading assetss3://acme-updates · 412 files · 2.1 MB1.4s

Publishing to branchproduction0.2s

Update 1784952901 is live on production

devices pick it up on their next check-in

~/acme-app $

$ eoas publish$ eoas rollback$ eoas republish

Manage updates from CLI and CI

Publish, roll back and republish are eoas commands. Run them by hand or script them in your pipeline.

ChannelproductionBranchproduction

Channels decide who gets what

Each build ships with a channel baked in. Point the channel at a branch, and that's what those builds receive.

25%

Progressive rollouts

Ship to a slice of the branch, watch the health chart, raise it. Deterministic, no per-device state.

Your storage. Your CDN.

Bundles land in a bucket you own, and devices download from your CDN, or over signed URLs into a private bucket.

Catch the regression while it's small.

Every crash, metric, event and log carries the exact update that produced it. A regression shows up while the rollout is still on a slice of the branch, and a republish reverts it.

Explore Observe →
ota.acme.internal/metrics

Metrics

SDK 56+Is the served update group healthy, and is the app fast for everyone?
AllLive24h
Split charts byDeviceOS versionCountryScreenUpdate groupUpdateApp versionPlatform
Update group ad83acfbClear all

Health over time

Near-real-time health, retained in one-minute buckets.

24/07/2026, 17:30:00 → 25/07/2026, 17:30:00

Synced 17:30:19

HealthAdoptionFaults

Successful devices across all attempts

100%50%0%
18:0000:0006:0012:00
Update groupDevicesFailedHealth
feat(observe): Implement health history tracking and reportingad83acfb · production · runtime 3.0.0 · 24 Jul, 16:543321196.7%

Bundle load

468msp50806msp901.26sp99
332 devices · 2,546 samples
500ms400ms
18:0000:0006:0009:00

Time to first render

1.22sp502.10sp903.19sp99
332 devices · 2,546 samples
1.40s1.20s1.00s
18:0000:0006:0009:00

Time to interactive

2.73sp504.69sp907.13sp99
329 devices · 2,542 samples
3.00s
18:0000:0006:0009:00

Cold launch

1.57sp502.72sp904.21sp99
328 devices · 2,540 samples
1.80s1.60s
18:0000:0006:0009:00
NATIVESIGABRT · ContentView12 devices
JSTypeError: cart is undefined31 devices
update 3.4.2

Native and JS crashes

Both land tied to the update that shipped them, with the device count for each, so you see within minutes when a release breaks something.

deviceosregionscreen
Checkout4.9s
Home2.4s

Split health by device, OS, region and screen

Every metric breaks down by device model, OS, region, app version and screen, straight from react-navigation.

EVENTcheckout_completed
LOGpayment retry, attempt 2
ERRORTypeError: cart is undefined

Send your own events and logs

Ship structured events and logs from the app, and read them next to the delivery data, filtered by the same update, branch and channel.

expo-observe ClickHouse
GrafanaPostHogDatadog

Your ClickHouse, your tools

The data lives in a database you own. Put Grafana, PostHog or Datadog on it, or query it raw.

device e3a5e62c
tenant: acme-corpplan: procohort: beta

Custom device propertiesEnterprise

Tag devices with your own attributes and read health for one tenant, plan or cohort instead of the whole fleet.

Talk to your control plane.

xprem ships an MCP server, so agents reach the same control plane you do: branches, channels, updates, rollouts and the whole Observe dataset. Agents sign in with OAuth as dashboard users, and every call runs with that user's per-app permissions. Nothing more.

Claudexprem mcp · oauth · acme-app

Which screens had the worst time to interactive since 3.4.2 rolled out?

xprem · queried Observe

Checkout regressed on 3.4.2. The other screens are flat against 3.4.1:

Checkout4.9s
Search2.9s
Home2.4s
Profile2.1s

time to interactive · p90 · last 24 h · 3.4.2 vs 3.4.1

Want me to roll 3.4.2 back? You have that permission on acme-app.

Reply to Claude…

Your permissions, app by app

Agents sign in over OAuth as a dashboard user, and inherit exactly what that user can do on each app. No API keys.

Operate releases from chat

List branches, channels and updates, check a rollout, then roll back or republish, the same operations the dashboard exposes.

Ask your data anything

Plain-language questions over metrics, events and logs. The agent answers straight from Observe.

Deploy everywhere.

xprem is one Go process. It holds no session state, so you run as many replicas as you need behind your load balancer and they stay consistent on their own.

Docker

ghcr.io/mercuretechnologies/expo-open-ota

One image, one env file. The same container runs on a laptop, on a VM and on your orchestrator.

Kubernetes

Helm chart

Deploy with the Helm chart and scale the deployment like any other stateless service in your cluster.

Go binary

single static binary

No runtime to install, no sidecars. Drop the binary on a box, give it its environment, run it under systemd.

Scale it like any stateless service.

Update checks are read-heavy and cheap. Add replicas behind a load balancer and they all resolve the same manifest from the same Postgres, with no leader, no sticky sessions and no coordination between them. Assets never pass through the process, so a replica only ever handles small JSON.

Open the deployment guide ↗
.envself-hosted
BASE_URL=https://ota.acme.internal
DB_URL=postgres://xprem:•••@postgres/xprem
CLICKHOUSE_URL=clickhouse://clickhouse:9000/xprem
STORAGE_MODE=s3
CDN_BASE_URL=https://cdn.acme.internal

$ docker run --env-file .env \

ghcr.io/mercuretechnologies/expo-open-ota:latest

postgres ready · clickhouse ready · listening on :3000
one processno hosted dependencyyour network boundary

Why teams run their own.

EAS Update is the fastest way to get OTA updates running on a small app. These are the reasons teams move to self-hosted infrastructure instead.

No per-user pricing.

Nothing is metered. Unlimited devices and unlimited updates, for the cost of the server, the database and the bucket you already run.

Advanced update and observability features.

Progressive rollouts, channel splits across branches, per-update health, metrics, events and logs. Built on the MIT expo-updates packages, the official release path for React Native.

Runs on a private or offline network.

Air-gapped deployments, internal apps and regulated environments. No outbound connection, no telemetry, and Enterprise licence keys verify offline.

You control the scaling.

Stateless replicas behind your own load balancer, your CDN and the regions you choose. Capacity and rate limits are set by your infrastructure.

You own the data.

Update history in your Postgres, runtime signals in your ClickHouse, bundles in your bucket. Retention, residency, access rules and audit trail are yours to configure.

No intermediary in the path.

Device to your server to your edge, over private links and internal DNS, inside the regions you already operate in.

EAS UpdateExpo, hostedClosed platformsCodemagic · Bitrise · Revopushxpremself-hosted, open core
PricingPer monthly active userPer active user or per requestYour infrastructure bill. Nothing is metered
Your data livesOn their infrastructureOn the vendor's infrastructureIn your Postgres, ClickHouse and bucket
GovernanceRetention and residency are theirsRetention and residency are theirsRetention, residency and access are yours
Private networkNeeds the public internetNeeds the public internetRuns air-gapped. No outbound call
ScalingTheir capacity, their limitsTheir capacity, their limitsYour replicas, your CDN, your regions
ObservabilityBasic delivery statsBasic delivery statsHealth, metrics, events and logs per update
SourceClosedClosedMIT core, readable and forkable
If they disappearYour release path stopsYour release path stopsIt keeps running. You own it

Fully open source.

Publishing, branches, channels, rollbacks, progressive rollouts, every storage backend, every CDN integration, the dashboard and the Prometheus metrics are MIT. Everything you need to run OTA updates in production, free. A feature released under MIT never moves behind the commercial licence.

Four things need a licence.

They live in ee/ directories in the same repository and ship in the same binary. The key is verified offline: the server never contacts us, sends no telemetry, and keeps working in an air-gapped network.

Enterprise

RBAC

Roles instead of one shared admin account. Decide who can publish, who can move a channel to another branch, and who can only look.

Enterprise

SSO

Sign in through your own identity provider, so access follows the accounts you already manage and revoke.

Enterprise

Branch protection

Mark the branches that reach real users as protected, then decide per API key whether it may publish to them. A sandbox CI job or a developer testing on staging gets a token that cannot touch production, instead of the one token that can do everything.

Enterprise

Custom device attributes

Attach your own attributes to logs, metrics and events, then slice by them. Your plan tier, your tenant, your feature flag, whatever your app knows about the device.

Read the licensing page ↗one binary · one Docker image · offline verification

Questions.

The things people ask before they commit to running their own release infrastructure.

Does xprem only work with Expo?

It requires expo-updates in your app, yes. That is the protocol xprem implements, and it is what lets you keep the standard runtime instead of a proprietary client. expo-observe is separate: without it xprem still runs your releases, health and rollouts, you just do not get the events, metrics and logs. We bet on the Expo ecosystem on purpose. The packages are MIT, the community is large, and expo-updates is the default release path for React Native.

Do I need an Expo account?

Not in control plane mode. xprem holds the release metadata itself and authenticates publishing with a per-app API key. Stateless mode is the exception: it uses Expo as the source of truth and does need an Expo access token.

What do I have to run?

One Go process, a Postgres database and an object storage bucket. Add ClickHouse only when you want the full Observe layer. Everything ships as a single Docker image, a Helm chart or a static binary.

Do I have to change my app to migrate?

You point expo-updates at your own server URL and publish through the eoas CLI. The manifest, the asset download and the runtime behaviour are the standard protocol, so the client side of your app stays as it is.

Where do the update bundles live?

In your bucket. S3, Google Cloud Storage, Azure Blob, Cloudflare R2, MinIO, DigitalOcean Spaces, Supabase Storage, any S3-compatible provider, or a local volume. xprem writes to it and hands devices a URL into it.

Can xprem run without internet access?

Yes. It runs inside a private or air-gapped network. There is no telemetry and no outbound connection, and Enterprise licence keys are verified offline, so the server keeps working with no route to the public internet.

How is it priced?

There is no per-user pricing. Nothing is metered, so unlimited devices and unlimited updates cost you the server, the database and the bucket you already run. The release engine is MIT; only RBAC, SSO, branch protection and custom device attributes need a commercial licence.

What is MIT and what is commercial?

The release engine is MIT: publishing, branches, channels, rollbacks, progressive rollouts, every storage backend, every CDN integration, the dashboard and Prometheus metrics. Enterprise adds RBAC, SSO, branch protection and custom device attributes, and lives in ee/ directories under a commercial licence. A feature released under MIT never moves behind the commercial licence.

Can I query the data with my own tools?

Yes. Observe writes into a ClickHouse you own, so you can point Grafana, Datadog, PostHog or plain SQL at it. Release state is in your Postgres. Nothing is locked behind an API we control.

How does xprem compare to EAS Update?

EAS Update is hosted by Expo and priced per monthly active user, and your update and device data lives on their infrastructure. xprem runs on yours: no metering, data in your own Postgres, ClickHouse and bucket, deployment on a private network, and per-update health, metrics, events and logs. EAS Update is the faster start for a small app; xprem is for teams that need to own the release path.

Is it affiliated with Expo?

No. xprem is an independent open-source project. It is not affiliated with, endorsed by, or supported by Expo (650 Industries, Inc.).

Everything above is covered in more depth in the documentation.