Expo OTA (Over-The-Air) Updates Deployment Guide
This guide covers the complete OTA update workflow for the encache mobile app, including publishing updates, monitoring deployments, and rolling back if needed.
Table of Contents
- Overview
- Prerequisites
- Publishing Updates
- Monitoring Updates
- Rollback Procedures
- Troubleshooting
- FAQ
Overview
The Expo OTA (Over-The-Air) system allows distributing app updates without requiring app store submission. Updates can be deployed to multiple independent channels (development, staging, production) with immediate global availability.
Key Features
- No App Store Review: Deploy updates instantly
- Multiple Channels: Independent development, staging, and production environments
- Automatic Distribution: Devices receive updates on next launch
- Rollback Support: Quickly revert to previous versions
- Version Tracking: Monitor which devices have which version
Architecture
┌─────────────┐
│ Developer │
│ Pushes OTA │
└──────┬──────┘
│ eas update --channel <channel>
↓
┌─────────────────────┐
│ EAS Update Service │
│ (Expo's CDN) │
└──────┬──────────────┘
│ Distributes via global CDN
↓
┌──────────────────────┐
│ Mobile Devices │
│ (All channels) │
│ Check on app launch │
└──────────────────────┘
Prerequisites
Before you can publish OTA updates, ensure the following are completed:
Local Setup
-
Install Expo CLI
-
Authenticate with Expo
This creates credentials at ~/.expo/. You'll need an Expo account linked to the encache project.
-
Install Dependencies
-
Environment Variables
For CI/CD systems, set the EXPO_TOKEN environment variable:
Get your token:
Project Configuration
The following files must be properly configured:
apps/mobile/app.json: Definesupdates.urlpointing to EAS Update serviceapps/mobile/eas.json: Defines build profiles and channelsapps/mobile/package.json: Must includeexpo-updatesdependencyapps/mobile/index.tsx: Must initializeexpo-updateson startup
Verify configuration:
cd apps/mobile
# Validate app.json
node -e "
const config = require('./app.json');
console.log('✓ OTA URL:', config.expo.updates.url);
console.log('✓ Auto-check:', config.expo.updates.checkAutomatically);
"
# Validate eas.json
node -e "
const config = require('./eas.json');
console.log('✓ Channels:', config.update.channels.join(', '));
"
Publishing Updates
Quick Start: Publish to Staging
The simplest way to publish an update:
Or manually:
Detailed Publish Workflow
1. Ensure Code is Committed
All changes must be committed to git:
git add .
git commit -m "Fix: resolve memory leak in background task"
git push origin feature/memory-fix
The OTA system records the commit hash with each update for audit purposes.
2. Publish to Staging First
Always test in staging before production:
Expected output:
3. Monitor Staging Devices
Check the EAS dashboard to verify: - Update appears in the "staging" channel - Devices subscribed to staging are downloading - No error messages in device logs
4. Promote to Production
Once staging is validated (typically 30+ minutes of device usage):
Advanced: Publishing from CI/CD
The GitHub Actions workflow automatically publishes updates:
- On push to
main: Publishes tostagingchannel - On push to
release/*: Publishes toproductionchannel - Manual trigger: Allows custom channel and message
Example: Trigger manual release
Channel Promotion Workflow
Recommended promotion path:
Feature Branch
│
├─→ [development] (developer's local branch)
│
├─→ [staging] (test on staging devices)
│ │ Wait 30+ minutes for device coverage
│ │ Monitor error reports
│ │
└─→ [production] (release to all users)
│ Monitor error reports
│ Be ready to rollback if needed
Monitoring Updates
View Recent Updates
List all updates on a channel:
Output:
Channel: staging
ID: <uuid> Version: v1.2.4 Created: 5 minutes ago
ID: <uuid> Version: v1.2.3 Created: 2 hours ago
ID: <uuid> Version: v1.2.2 Created: 1 day ago
View Device Status
See which devices have which version:
# Via EAS dashboard (recommended for visual view)
open https://expo.dev
# Via CLI
eas device list
eas update list --channel production --limit 10
Monitor Error Reports
Devices report errors during update checks:
# Monitor error rates
eas update list --channel production
# Check specific update
eas update view <update-id>
Application Metrics
In your app, monitor OTA-related metrics:
// expo-updates does not expose an addListener API.
// Track OTA events by checking the result of checkForUpdateAsync/fetchUpdateAsync:
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
analytics.track('ota_update_applied', { channel: Updates.channel });
await Updates.reloadAsync();
}
Rollback Procedures
Scenario: Production Bug After Update
If a production update causes issues:
- Assess Impact
- How many devices are affected?
- Is the impact critical?
-
Can users work around it?
-
Decide: Fix or Rollback?
-
Rollback: If the issue is critical (crashes, data loss) → immediate rollback
- Fix: If the issue is minor → prepare hotfix and republish
Immediate Rollback
Revert production to the previous known-good version:
# Find the update group ID to roll back to
eas update list --channel production --limit 5
# Re-publish a previous update group (true rollback — no code checkout needed)
eas update:republish --group <previous-update-group-id> --branch production
The system will: 1. Publish the old update as the new "current" version 2. Within 30 seconds, devices will check for updates 3. Within 5 minutes, 95%+ of devices will have reverted
Hotfix Release
Prepare a hotfix and release:
# Create hotfix branch
git checkout -b hotfix/crash-fix
# Make the fix
# ... edit files ...
# Commit and push
git add apps/mobile/src/**
git commit -m "Hotfix: resolve crash on launch"
git push origin hotfix/crash-fix
# Create PR and merge to main
gh pr create --title "Hotfix: crash" --body "Critical crash fix"
# Once merged to main, the CI pipeline automatically publishes to staging
# After validation, publish to production:
eas update --channel production --message "Hotfix v1.2.4: crash on launch"
Preventing Rollbacks
- Test thoroughly before release
- Use staging for 30+ minutes before promoting to production
-
Test on multiple device types
-
Monitor closely after release
- Check error reports 5 minutes after publication
-
Have team members test the app
-
Use feature flags
- Enable new features gradually using feature flags
- Disable problematic features without a full rollback
Troubleshooting
Issue: Update Not Downloading
Symptoms: Devices don't receive updates even after 5+ minutes
Solutions:
-
Verify device is on correct channel
-
Verify network connectivity
- Device must have internet access
-
Check firewall/proxy settings
-
Check update availability
-
Force update check (in app)
Issue: Build Fails During CI/CD
Symptoms: GitHub Actions workflow fails at "Build app" step
Solutions:
-
Check EAS token is valid
-
Verify project ID in app.json matches EAS
-
Check for uncommitted changes
-
View detailed logs
Issue: "Bundle Size Exceeds Limit"
Symptoms: eas update fails with "bundle exceeds 50MB"
Solutions:
-
Analyze bundle size
-
Implement code splitting
-
Remove unused dependencies
-
Compress assets
- Optimize images (use WebP)
- Remove unused fonts
Issue: "Uncommitted Changes" Error
Symptoms: publish-update.sh fails with "uncommitted changes"
Solutions:
# Commit all changes
git add .
git commit -m "Description of changes"
# Or discard changes
git checkout apps/mobile/
git clean -fd apps/mobile/
Issue: Authentication Fails
Symptoms: eas update fails with "authentication error"
Solutions:
-
Re-authenticate
-
Create new token for CI/CD
-
Verify credentials
FAQ
Q: Can I schedule an update to publish at a specific time?
A: No, EAS Update publishes immediately when eas update runs. To schedule: - Use GitHub Actions scheduled workflows - Trigger via CI/CD at a specific time
Q: How long do devices take to receive an update?
A: - Check interval: Every app launch (configurable via checkAutomatically) - Download time: Usually <1 minute on 4G - Apply time: Next app launch - Global coverage: 95%+ within 5 minutes
Q: Can I target specific devices?
A: No, OTA updates are channel-based. All devices on a channel receive the same version. To target subsets: - Use multiple channels - Use feature flags in app code - Use staged rollout (publish to staging first)
Q: What's the maximum update size?
A: 50MB for the entire update bundle. This includes: - JavaScript code - Assets (images, fonts, sounds) - Dependencies bundled with the app
Measure bundle size:
Q: Can I revert a published update?
A: Yes, publish the old version again:
eas update list --channel production --limit 2
eas update --channel production --message "Rollback to previous version"
Q: Do updates require app restart?
A: - First time: No, app reloads without restart (if possible) - Subsequent times: App reloads on next launch
Q: Can I use OTA updates with native code changes?
A: No, OTA updates are JavaScript/asset only. For native changes: - Update native code - Rebuild via eas build - Submit to app stores
Q: How are updates signed and verified?
A: Expo automatically signs and verifies updates: - All updates signed with certificate - Device verifies signature before applying - Corrupted updates are rejected
Q: How much does OTA updates cost?
A: Expo OTA Updates is included with EAS Build. Bandwidth is free for updates (paid separately only if you exceed limits).
Check pricing: https://expo.dev/pricing
Support
For additional help:
- Expo Documentation: https://docs.expo.dev/deploy/send-over-the-air-updates/
- EAS Update Docs: https://docs.expo.dev/eas-update/getting-started/
- Community: https://forums.expo.dev
- Report Issues: https://github.com/expo/expo/issues
Changelog
v1.0.0 (2026-05-10)
- Initial OTA setup with development, staging, production channels
- Automated CI/CD pipeline via GitHub Actions
- Configuration validation tests
- Rollback procedures documented