Mobile Deployment Patterns
Deploy React Native apps with EAS Build, EAS Submit, OTA updates, and automated CI/CD pipelines
When to Use
- Preparing a React Native app for App Store and Google Play submission
- Setting up automated build and release pipelines
- Implementing over-the-air (OTA) updates for instant bug fixes
- Managing multiple environments (dev, staging, production)
- Automating version bumping and changelog generation
Instructions
- Configure EAS Build profiles for each environment.
// eas.json
{
"cli": { "version": ">= 8.0.0" },
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": { "APP_ENV": "development" },
"ios": { "simulator": true }
},
"preview": {
"distribution": "internal",
"env": { "APP_ENV": "staging" },
"channel": "preview"
},
"production": {
"env": { "APP_ENV": "production" },
"channel": "production",
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"appleId": "developer@company.com",
"ascAppId": "1234567890",
"appleTeamId": "ABCDE12345"
},
"android": {
"serviceAccountKeyPath": "./play-store-key.json",
"track": "internal"
}
}
}
}
- Use environment-specific configuration in
app.config.ts.
const IS_PROD = process.env.APP_ENV === 'production';
const IS_STAGING = process.env.APP_ENV === 'staging';
export default {
name: IS_PROD ? 'MyApp' : IS_STAGING ? 'MyApp (Staging)' : 'MyApp (Dev)',
slug: 'my-app',
ios: {
bundleIdentifier: IS_PROD ? 'com.company.myapp' : 'com.company.myapp.dev',
},
android: {
package: IS_PROD ? 'com.company.myapp' : 'com.company.myapp.dev',
},
extra: {
apiUrl: IS_PROD
? 'https://api.company.com'
: IS_STAGING
? 'https://api.staging.company.com'
: 'https://api.dev.company.com',
},
};
- Build and submit with EAS CLI.
# Build for production
eas build --platform all --profile production
# Submit to stores after build completes
eas submit --platform ios --profile production
eas submit --platform android --profile production
# Build and auto-submit in one command
eas build --platform all --profile production --auto-submit
- Implement OTA updates with EAS Update for instant JavaScript-only fixes without store review.
npx expo install expo-updates
// app.config.ts
export default {
updates: {
url: 'https://u.expo.dev/your-project-id',
},
runtimeVersion: {
policy: 'appVersion', // or 'sdkVersion', 'fingerprint'
},
};
# Publish an OTA update to the production channel
eas update --branch production --message "Fix checkout button alignment"
# Publish to preview channel
eas update --branch preview --message "Test new onboarding flow"
- Check for and apply updates in the app.
import * as Updates from 'expo-updates';
async function checkForUpdates() {
if (__DEV__) return; // Updates do not work in development
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
// Restart to apply the update
await Updates.reloadAsync();
}
} catch (error) {
console.error('Update check failed:', error);
}
}
// Check on app foreground
useEffect(() => {
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') checkForUpdates();
});
return () => subscription.remove();
}, []);
- Set up CI/CD with GitHub Actions.
# .github/workflows/build.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm test
build:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: npm ci
- run: eas build --platform all --profile production --non-interactive --auto-submit
update:
needs: test
if: github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[native]')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: npm ci
- run: eas update --branch production --message "${{ github.event.head_commit.message }}"
- Manage version numbers. Use
autoIncrementin EAS Build for automatic build number bumps. Semantic version theversionfield inapp.config.ts.
# Bump version
npm version patch # 1.0.0 -> 1.0.1
npm version minor # 1.0.0 -> 1.1.0
npm version major # 1.0.0 -> 2.0.0
- Test the production build before submission. Build an internal distribution build and test on physical devices before submitting to stores.
eas build --platform ios --profile preview
# Install via QR code or direct link from EAS dashboard
Details
OTA update limitations: OTA updates can only change JavaScript and assets. Changes to native code (new native modules, permission changes, SDK upgrades) require a new native build through the stores. Use runtimeVersion with fingerprint policy to automatically detect when a native build is needed.
App Store review tips:
- First submission takes 1-3 days; subsequent updates typically 24 hours
- Include clear screenshots and a demo account for reviewers
- Privacy policy is required; describe all data collection
- Do not mention competing platforms in metadata
Google Play review: Initial review takes hours to days. Use internal testing tracks for team testing, closed testing for beta users, and production for release.
Release strategy:
- Merge to main triggers CI
- CI runs tests, builds, and submits to internal testing
- QA tests on internal track
- Promote to production (manual approval)
- Post-release JS fixes via OTA updates
Common mistakes:
- Shipping debug builds to production (check build profile)
- Not testing OTA updates before publishing to production
- Forgetting to update
runtimeVersionwhen native dependencies change - Not handling update download failures gracefully in the app
Source
https://docs.expo.dev/deploy/build-project/
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.