xprem: a self-hosted Expo Updates server for OTA releases
Self-hosted Expo OTA Updates alternative to EAS Update, on the official Expo Updates protocol. Multi-app dashboard, progressive rollouts, instant rollbacks, and per-update crashes, metrics and logs from every device via expo-observe. Your bucket, your CDN, your data. Formerly expo-open-ota.
At a glance
- What is it?
- xprem is a Go server that speaks the official Expo Updates protocol, so an app already using expo-updates can point at it instead of EAS Update. It adds branch and channel mapping, progressive rollouts, A/B splits, manifest signing with your own key, and telemetry collection into your own ClickHouse, at the cost of running the storage, CDN and database yourself.
- Who is it for?
- xprem suits teams already shipping React Native apps with expo-updates that want to keep bundles, telemetry and signing keys in their own infrastructure, since it needs nothing more than a Postgres database, an S3-compatible bucket and a CDN. It is the wrong tool if you want a managed service with no operations work, since every moving part, including ClickHouse for telemetry, is yours to run.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An Expo Updates server that runs on your own machines
The problem xprem addresses is narrow and real. EAS Update is the hosted path for shipping JavaScript bundle updates to Expo and React Native apps, and teams that want their bundles, their telemetry and their signing keys inside their own perimeter have to run the update protocol themselves. xprem is that server, written in Go, and it speaks the official Expo Updates protocol, so any app running expo-updates can talk to it without changing the client side. The project was formerly known as Expo Open OTA, and it is positioned as an open-source alternative to EAS Update on your own servers. The project states that it has served OTA updates in production since early 2025, to apps totalling more than a million monthly active users, which is a claim by the maintainers rather than something you can verify from the repository, but it does tell you the intended scale of the design. Everything else in the project follows from the same premise: the server is one process, the data stays in your infrastructure, and the operational surface is small enough to run as a replica set.
Branches, channels and deterministic A/B splits
The release model is the part that goes beyond simple hosting. You build your apps against release channels, for example production and staging, and map those channels to specific branches on the server. A branch holds a version of the bundle, and a channel points at it. From there the server can serve an update to a percentage of your user fleet, which is how progressive rollout is done: you ship to a small slice, watch the health of the update, then widen or roll it back. A/B testing is a second branch served by the same channel at the same time, with devices split deterministically between the two, so a device does not flip between variants on each request. Rollbacks and republishing are driven from the eoas CLI, in CI or from the dashboard. A single server can host multiple applications, which is what makes one instance useful to several product teams in the same company, provided each app keeps its own branches and channels.
Manifest signing is the part that makes a self-hosted server safe
End-to-end code signing is the feature that separates this from a plain file server. The server signs every manifest with a private key that you own, and the matching certificate ships inside the app. The client side then refuses any update that does not verify against that certificate, so a compromised bucket or CDN cannot push arbitrary code into your application. That is the central risk of running your own update infrastructure: the storage layer holds bundles that your app will execute. Signing moves the trust anchor into the app binary, where an attacker with only bucket access cannot reach it. The key store options reflect the same assumption that the key matters more than the bundle: AWS Secrets Manager, environment variables, local key files, or a key sealed in the database. The Makefile also carries a lint rule that requires every source file under a directory named ee/ to have the enterprise licence header, which tells you the key handling code and the enterprise features are treated as separate surfaces.
Storage, delivery, cache and keys are all replaceable
Nothing about the delivery path is hard coded. For storage the project lists AWS S3, Google Cloud Storage, Azure Blob Storage, Cloudflare R2, MinIO, DigitalOcean Spaces, Supabase Storage, any S3-compatible storage, and the local file system. For delivery you can use CloudFront, a custom CDN domain, S3 presigned URLs, GCS signed URLs, Azure SAS URLs, or direct serving. The cache layer is Redis or an in-memory option, and the key store has the four choices listed earlier. The division of labour is the standard one: xprem uploads each bundle and its assets to your bucket, and devices download them from your CDN, so the storage cost and the bandwidth cost stay in the same hands as the rest of your data. The documentation says most providers are already supported and that a missing one is worth an issue. That breadth is what makes the project portable, and it is also what makes the configuration surface larger than a hosted service would ask of you.
Device telemetry lands in your own ClickHouse, and an MCP server ships with it
Observability is a first-class feature rather than a bolt-on. The server is compatible with expo-observe, which reports logs, metrics and events back from your apps, and it stores everything in your own ClickHouse instance. The repository layout shows how deep that goes: a clickhouse service in the compose file, a grafana/ directory, a prometheus.yml, and the ClickHouse Go driver in the module list. The README also notes per-update crashes, metrics and logs from every device, which is the input a rollout decision needs, since a progressive rollout is only as good as the health signal you watch while it widens. There is also an MCP server in the product itself, so agents can operate branches, channels and rollouts and query telemetry over OAuth, with per-user permissions. A documentation MCP server is published separately for Cursor and VS Code. The security model for the agent path is OAuth with per-user permissions, which is a different posture from the API keys most update servers use.
One Go binary, a Docker image, a Helm chart, or a stateless mode
Deployment options are deliberately layered. The project is one Go process, deployed as a single instance or as many replicas as needed, and it ships a Docker image and a Helm chart. There is also a stateless mode that runs without any database. The development stack in the repository is a compose file with a Postgres service, a ClickHouse service and the server itself, and the local database configuration is plain environment variables:
postgres:
image: postgres:latest
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secret}
POSTGRES_DB: ${POSTGRES_DB:-expo_ota_dev}
ports:
- "5432:5432"The build itself is a multi-stage image. A Node stage compiles the dashboard, a Go stage compiles the server, and a small Alpine stage runs it as a numeric user rather than by name, so Kubernetes can verify runAsNonRoot without extra configuration:
FROM --platform=$BUILDPLATFORM node:24-alpine AS dashboard-builder
WORKDIR /app/apps/dashboard
COPY apps/dashboard/package.json apps/dashboard/package-lock.json ./
RUN npm ciThe Go module targets a recent toolchain and pulls in the storage, database and observability clients you would expect:
module xprem
go 1.26.0The Makefile ties the workflow together with build, up, down, sqlc and lint targets, where sqlc regenerates the database access code from the SQL files and the lint target runs staticcheck and a dead code check. The dead code check has a comment explaining its purpose, which is a good sign about how the project treats unused exported functions.
The published benchmark, and the operational cost it leaves you with
The project publishes a benchmark with its method, which is unusual and useful. The setup was a single server on an EC2 c6g.medium with 1 vCPU and 2 GiB of RAM, an RDS PostgreSQL db.t4g.small database, Google Cloud Storage for bundles, and both code signing and device telemetry switched on. Against traffic from a fleet of one million monthly active users it recorded 230 requests per second with a mean of 1.46 ms, a p95 of 1.58 ms and a p99 of 2.35 ms with no errors. A capacity probe reached 650 requests per second with a 1.02 ms mean, and a full-fleet rollout peaked at 938 requests per second with a 3.15 ms mean but a p99 of 55.2 ms. The tail during rollout is the number to watch, since that is when most devices fetch a new bundle at once. The trade-off in all of this is that none of the infrastructure is free or managed: you run Postgres, the object store, the CDN, Redis if you want a shared cache, and ClickHouse if you want the telemetry, and the licence is a custom one that the repository metadata says GitHub cannot classify, with the badge describing MIT plus Enterprise and an ee/ directory that the lint target polices. The last push was on 2026-10-11 and the recent tags are v3.2.6 on 2026-10-10 with two beta tags before it.
Editorial conclusion
xprem suits teams already shipping React Native apps with expo-updates that want to keep bundles, telemetry and signing keys in their own infrastructure, since it needs nothing more than a Postgres database, an S3-compatible bucket and a CDN. It is the wrong tool if you want a managed service with no operations work, since every moving part, including ClickHouse for telemetry, is yours to run. Before switching, confirm the eoas CLI publish flow matches your current CI and read the licence terms in LICENSE.md, since the repository carries both MIT and Enterprise components and the ee/ directory is checked by the lint target.
Frequently asked questions
What is xprem and how does it differ from EAS Update?
xprem is a self-hosted Go server that implements the official Expo Updates protocol, so apps running expo-updates can point at it. It is the open-source alternative to EAS Update, with the bundles, telemetry and signing keys kept in your own infrastructure.
Does xprem support progressive rollouts and rollbacks?
Yes. You can serve an update to a percentage of your fleet and roll it back, and A/B testing is done by letting one channel serve two branches with devices split deterministically between them. Publishing, rolling back and republishing happen from the eoas CLI, in CI or from the dashboard.
How does xprem stop a compromised bucket from pushing code to my app?
The server signs every manifest with a private key you own, and the matching certificate ships inside the app, so expo-updates refuses any update that does not verify against it. The key can live in AWS Secrets Manager, environment variables, local key files, or sealed in the database.
What infrastructure do I need to run xprem myself?
You need a Postgres database, an S3-compatible bucket such as S3, GCS, Azure Blob Storage, R2, MinIO, DigitalOcean Spaces or Supabase Storage, and a CDN, plus Redis or the in-memory cache if you want a shared one. ClickHouse is used for the expo-observe telemetry, and a stateless mode runs without a database.
Which licence does xprem use?
The repository carries a LICENSE.md file, the badge describes the terms as MIT plus Enterprise, and the repository metadata classifies the licence as a custom one GitHub cannot classify. Source files under a directory named ee/ must carry the enterprise licence header, and the Makefile lint target enforces that.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mercuretechnologies-xprem)Was this useful?