English | 简体中文
This guide deploys the React Native Hotfix update server on a private VM, company network, or private cloud. It covers Docker, a native Node.js service, TLS, publishing access, backup, upgrades, and security.
The included server is a single-node reference deployment. It is suitable for an internal update service with one active writer. Do not run multiple writer instances against separate local disks or expect the local channel files to provide distributed transactions.
Publisher CLI
|
| HTTPS or SSH tunnel + admin token
v
Private server
- /v1/admin/* publishing API
- /v1/check app update check
- /v1/updates/* immutable update files
- persistent disk manifests, bundles, assets, channel state
^
| HTTPS through reverse proxy
|
React Native apps / internal CDN
The private signing key and admin token never enter the mobile app. Mobile clients contain only the RSA public key.
- Linux server or VM with a persistent data volume
- Docker with Compose, or Node.js
22.11+ - A DNS name reachable by target devices
- A trusted HTTPS certificate
- Outbound network access only when installing images/packages
- A separate secure location for backups
- Accurate system time, preferably using NTP
Public app endpoints must be reachable over HTTPS. The admin endpoints should be available only through a VPN, private admin hostname, IP allowlist, or SSH tunnel.
Generate the key pair on a trusted publisher machine:
npx hotfix keys generate --out .hotfix/keysKeep these values separate:
.hotfix/keys/public-key-der-base64.txt: embed in Android and iOS..hotfix/keys/private.pem: copy to the server secret store..hotfix/keys/key-id.txt: record with the deployed version.
Never commit the private key. Do not mount the directory containing the entire key-generation workspace into the server container.
Create a secret directory outside Git:
mkdir -p .secrets
cp .hotfix/keys/private.pem .secrets/hotfix_private_key.pem
openssl rand -hex 32 > .secrets/hotfix_admin_token
chmod 600 .secrets/hotfix_private_key.pem .secrets/hotfix_admin_tokenVerify that .secrets/ is ignored by Git before continuing.
For a production server, the equivalent secure stores are Docker secrets, Kubernetes Secrets, Vault, AWS Secrets Manager, or an HSM/KMS-backed secret injection mechanism.
The repository includes:
deploy/Dockerfiledeploy/docker-compose.ymldeploy/Caddyfile.example
Set the public update origin:
export HOTFIX_BASE_URL=https://updates.example.com
docker compose -f deploy/docker-compose.yml up -d --buildThe container:
- runs as an unprivileged
hotfixuser; - exposes port
3000only on host loopback; - persists
/datain thehotfix-datavolume; - reads the private key and admin token from Docker secrets;
- includes a Docker health check.
Check health:
curl http://127.0.0.1:3000/health
docker compose -f deploy/docker-compose.yml ps
docker compose -f deploy/docker-compose.yml logs --tail=100 hotfixUse an immutable image tag and registry mirror for regulated or air-gapped
environments. Pin HOTFIX_VERSION in the Compose build arguments and record
the resulting image digest.
Create a dedicated service account and directories:
sudo useradd --system --home-dir /opt/react-native-hotfix \
--shell /usr/sbin/nologin hotfix
sudo install -d -o hotfix -g hotfix /opt/react-native-hotfix
sudo install -d -o hotfix -g hotfix /var/lib/react-native-hotfix
sudo install -d -o root -g hotfix -m 750 /etc/react-native-hotfixInstall the server package:
sudo npm install --prefix /opt/react-native-hotfix \
react-native-hotfix-server@0.1.3Install secrets:
sudo install -o root -g hotfix -m 640 \
.secrets/hotfix_private_key.pem \
/etc/react-native-hotfix/hotfix_private_key.pemCreate /etc/react-native-hotfix/server.env from
deploy/server.env.example and set a strong admin token:
HOTFIX_STORAGE_DIR=/var/lib/react-native-hotfix
HOTFIX_PRIVATE_KEY=/etc/react-native-hotfix/hotfix_private_key.pem
HOTFIX_ADMIN_TOKEN=replace-with-at-least-32-random-characters
HOTFIX_BASE_URL=https://updates.example.com
HOTFIX_MAX_BYTES=209715200
HOST=127.0.0.1
PORT=3000
Secure the environment file:
sudo chown root:root /etc/react-native-hotfix/server.env
sudo chmod 600 /etc/react-native-hotfix/server.envCreate /etc/systemd/system/react-native-hotfix.service:
[Unit]
Description=React Native Hotfix server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=hotfix
Group=hotfix
WorkingDirectory=/opt/react-native-hotfix
EnvironmentFile=/etc/react-native-hotfix/server.env
ExecStart=/opt/react-native-hotfix/node_modules/.bin/hotfix-server
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/react-native-hotfix
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now react-native-hotfix
sudo systemctl status react-native-hotfix
curl http://127.0.0.1:3000/healthThe service binds only to loopback. A reverse proxy provides HTTPS.
Use deploy/Caddyfile.example as a starting point:
updates.example.com {
encode zstd gzip
@admin path /v1/admin/*
respond @admin 404
reverse_proxy 127.0.0.1:3000 {
header_up X-Forwarded-Proto {scheme}
}
}This exposes update checks/files to mobile clients while returning 404 for
public admin requests. Publish through a private route or SSH tunnel.
If the environment uses a private CA, install its certificate chain and ensure mobile devices trust that CA.
An equivalent Nginx location must:
- terminate TLS with a valid certificate chain;
- proxy
/v1/checkand/v1/updates/to127.0.0.1:3000; - deny
/v1/admin/unless the request comes from a private network or identity proxy; - allow request bodies at least as large as
HOTFIX_MAX_BYTES; - preserve
Hostor configureHOTFIX_BASE_URL.
Example admin isolation:
location ^~ /v1/admin/ {
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 200m;
}Use an identity-aware proxy in addition to IP allowlisting when publishers connect from mobile or untrusted networks.
Development on the server host:
http://127.0.0.1:3000/v1/check
Android emulator:
http://10.0.2.2:3000/v1/check
Private production deployment:
https://updates.example.com/v1/check
The app must use the public key matching the server private key and the same channel/runtime version used for publishing. See the integration guide for exact Android/iOS configuration.
The public proxy blocks /v1/admin/*. Choose one of these publishing paths:
ssh -N -L 3000:127.0.0.1:3000 ops@updates.example.comIn another terminal:
HOTFIX_ADMIN_TOKEN='<secret>' \
npx hotfix publish \
--server http://127.0.0.1:3000 \
--input .hotfix/dist/ios-1.0.0 \
--rollout-percent 5The SSH tunnel only protects the admin connection. HOTFIX_BASE_URL must still
point to the HTTPS URL embedded in download responses.
Add an internal-only DNS record and TLS endpoint such as
updates-admin.corp.example.com. Permit only VPN or office networks, then publish
to that origin.
Do not expose the admin token in build logs, shell history, CI output, or mobile configuration.
The persistent directory contains immutable update releases and channel state:
/var/lib/react-native-hotfix/
pending/
published/
channels/
Back up this entire directory. Source maps remain on the publisher unless you intentionally archive them elsewhere.
Pause publishing and application writes before taking a filesystem snapshot, or use storage-level atomic snapshots.
Example archive:
sudo systemctl stop react-native-hotfix
sudo tar -C /var/lib -czf /secure-backups/react-native-hotfix-$(date +%F-%H%M).tgz \
react-native-hotfix
sudo systemctl start react-native-hotfixFor Docker:
docker compose -f deploy/docker-compose.yml stop hotfix
docker run --rm \
-v react-native-hotfix_hotfix-data:/data \
-v "$PWD/backups:/backup" \
alpine tar -C /data -czf /backup/hotfix-data.tgz .
docker compose -f deploy/docker-compose.yml start hotfixRestore into the same directory layout, preserve ownership and file modes, and
verify /health plus a staged update before moving traffic.
Back up the signing key and admin token separately. Encrypt backups and test restore procedures regularly.
- Record the current server version, image digest, key ID, and latest update ID.
- Take a data backup.
- Install the new
react-native-hotfix-serverversion in a staging deployment. - Verify health, check, publish, activation, and app download behavior.
- Replace the production container/service.
- Confirm health and publish a low-rollout test update.
Keep the previous image or package version available for server rollback. Published update IDs remain immutable.
Server deployment rollback and app update rollback are different:
- Server rollback restores the service version.
- Update rollback activates an earlier immutable update ID.
- Native rollback requires reinstalling the previous native app.
Activate a known-good update:
HOTFIX_ADMIN_TOKEN='<secret>' \
npx hotfix activate \
--server https://admin-or-tunnel-origin \
--channel production \
--platform ios \
--runtime-version 1.0.0 \
--update-id <known-good-id> \
--rollout-percent 100Mobile devices must trust the HTTPS certificate. For a private CA:
- distribute the enterprise root certificate according to company policy;
- configure Android network security/trust settings;
- configure iOS trust and MDM profiles;
- verify certificate renewal, SANs, and clock accuracy;
- do not fall back to cleartext HTTP in production.
For air-gapped deployment:
- mirror or copy the Node base image;
- install the server from an internal npm registry;
- mirror pinned package tarballs and dependency metadata;
- use internal DNS, TLS, and storage;
- bring signing keys through an approved offline process;
- keep NTP/time sources available for TLS and access tokens.
The provided server stores channel pointers in local files. A single active writer is required.
For multiple API replicas or high availability, replace the storage layer with:
- a transactional database for pointer/idempotency metadata;
- durable object storage for immutable files;
- a publish workflow with compare-and-swap or database transactions;
- shared distributed locking for upload staging.
Do not attach the same local volume to multiple hosts and assume concurrent publishes are safe.
Monitor:
/healthavailability and latency;- disk utilization and inode usage;
- update downloads and HTTP error rate;
- signature/hash failures;
- publish and activation errors;
- TLS certificate expiration;
- backup success and restore drills;
- unknown or unauthorized admin requests.
Add edge authentication, rate limiting, access logs, alerts, and retention controls for a production deployment.
- Private key permissions are restrictive and stored outside Git.
- Admin token is random and not present in app bundles.
- Public proxy blocks
/v1/admin/*. - HTTPS certificate is trusted by test devices.
- Persistent storage survives container/service restart.
-
/healthis green. - A staging update publishes and installs.
- The app activates the update after a full cold start.
- A deliberately bad update rolls back.
- Restore from backup has been tested.
- Monitoring and alerting are configured.