Self-hostedReact NativeGuide

May 2025 · 10 min read

Self-Hosted OTA Updates for React Native: Complete Guide

Over-the-air updates let you ship JavaScript changes to React Native apps without going through the App Store or Play Store review process. Self-hosting your OTA server gives you full control over the pipeline, predictable costs, and no dependency on a third-party service for a critical app function.

How OTA updates work in React Native

React Native apps consist of two layers: a native binary (compiled Objective-C/Swift or Java/Kotlin) and a JavaScript bundle. App Store rules require native binary changes to go through review. JavaScript bundle changes do not — they can be delivered at runtime.

An OTA update server works as follows:

  • Developer builds a new JavaScript bundle and uploads it to the server
  • On app launch, the app polls the server with its current version and platform
  • If a compatible update exists, the server responds with a manifest URL
  • The app downloads the new bundle and stores it locally
  • On next launch (or immediately), the app runs the new bundle

The key constraint: you can only change JavaScript. Native module additions, new permissions, or changes to the native binary require a full app store submission.

Why self-host instead of using a managed service

Managed OTA services (Expo EAS Updates, previously CodePush) handle hosting, CDN, authentication, and the update protocol. The tradeoffs for self-hosting:

Cost. Managed services charge per-MAU or per-update. At 50,000 MAU, Expo EAS costs $299/month. Self-hosted infrastructure for the same load: $15–40/month. The gap widens with scale.

Data residency. Regulated industries (finance, healthcare, government) often cannot use US-hosted third-party services for app distribution infrastructure. Self-hosting lets you pick the region and cloud.

Control. You own the full pipeline. No service shutdowns, pricing changes, or API deprecations that break your release flow. CodePush teams found out the hard way when Microsoft shut down App Center in 2025.

Auditability. Security teams can review exactly what runs in your update pipeline. Open-source means no black boxes.

Architecture of a self-hosted OTA server

A production OTA server needs five components:

1. API server

Handles manifest requests from apps, authentication, release management, and CLI communication. Must be low-latency since it sits in the critical path of app startup. Typically a Node.js service (NestJS or Express).

2. Bundle storage

JavaScript bundles are 1–10 MB each. You need object storage with public read access and a CDN for fast global delivery. Cloudflare R2 is the most cost-effective option — no egress fees. AWS S3 + CloudFront or GCS also work.

3. Database

Stores release metadata: version, channel, platform, bundle URL, checksum, created_at. Also stores user accounts, organizations, and API keys. Postgres is the standard choice. Neon (serverless Postgres) works well for variable load with low base cost.

4. Cache layer

Manifest responses are identical for all users on the same app version and channel. Caching them in Redis reduces database load and cuts manifest response time from ~50ms to ~5ms. Upstash provides serverless Redis with a generous free tier.

5. Dashboard and CLI

A web UI for managing releases, channels, rollbacks, and teams. A CLI for publishing updates from local or CI environments.

Infrastructure requirements

ComponentMinimumRecommendedWhy
API server512 MB RAM, 0.5 vCPU1 GB RAM, 1 vCPUManifest parsing + auth
DatabaseShared PostgresDedicated instanceRelease query performance
Bundle storageAny S3-compatibleCloudflare R2No egress fees
CacheOptionalRedis (Upstash free tier)Manifest latency
CDNVia R2/S3Cloudflare (automatic with R2)Global bundle delivery

The API server does not need to be powerful — manifest requests are lightweight. The main cost driver is bundle egress (how often users download new bundles). Cloudflare R2 eliminates egress fees, making it the clear choice for bundle storage.

Setting up Relay OTA

Relay OTA provides all five components as open-source software. Here is the recommended setup using Vercel (API), Neon (database), and Cloudflare R2 (storage).

Step 1: Clone and configure

git clone https://github.com/relayota/relay-ota
cd relay-ota
cp .env.example .env

Fill in your Neon connection string, R2 credentials, and a JWT secret.

Step 2: Run database migrations

cd apps/api
npx prisma migrate deploy

Step 3: Deploy the API

vercel deploy --prod

Or deploy to Railway, Render, or any Node.js host. The API is a standard NestJS application.

Step 4: Configure your React Native app

// app.json
{
  "expo": {
    "runtimeVersion": "1.0.0",
    "updates": {
      "url": "https://your-relay-instance.com/api/manifest",
      "enabled": true
    }
  }
}

Step 5: Publish your first update

npx relay-ota publish \
  --bundle ./dist \
  --channel production \
  --platform ios,android \
  --runtime-version 1.0.0

Your app will check for and apply this update on next cold launch.

CI/CD integration

Publish updates automatically on merge to main using GitHub Actions:

# .github/workflows/ota-publish.yml
name: Publish OTA Update
on:
  push:
    branches: [main]
    paths: ['src/**', 'package.json']

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx expo export --platform all
      - run: |
          npx relay-ota publish \
            --bundle ./dist \
            --channel production \
            --platform ios,android
        env:
          RELAY_OTA_TOKEN: ${{ secrets.RELAY_OTA_TOKEN }}
          RELAY_OTA_URL: ${{ secrets.RELAY_OTA_URL }}

Cost breakdown at scale

MAURelay OTA (self-hosted)Expo EAS UpdatesMonthly savings
1,000$1–5$0 (free tier)-$1–5
10,000$5–15$99$84–94
50,000$10–25$299$274–289
100,000$15–40$500+$460–485+
500,000$30–80$2,000+$1,920–1,970+

Self-hosting becomes economically positive above ~10,000 MAU, accounting for ~4 hours of setup time at typical developer rates. Above 50,000 MAU, the monthly savings justify ongoing maintenance easily.

Operational considerations

Before committing to self-hosting, plan for:

  • Uptime: The manifest endpoint is in the critical path of app startup. Use a managed compute platform (Vercel, Railway) with automatic restarts and health checks rather than a bare VPS.
  • Backups: Back up your Postgres database. Losing release metadata means losing rollback capability. Neon has point-in-time restore on paid plans.
  • Runtime version discipline: OTA updates only apply to apps whose native binary matches the runtimeVersion. Increment it whenever you ship a native binary update, or users on old binaries will get incompatible bundles.
  • Rollback plan: Always test rollback before your first production publish. Relay OTA supports one-click rollback via dashboard or CLI.

Get started

Relay OTA is open source under the MIT License. Full deployment guide, environment variable reference, and CI/CD examples are in the documentation.