Module 12: Production and Deployment
Building with EAS Build
Build production-ready iOS and Android apps in the cloud
🎯 Learning Objectives
- Understand EAS Build and its advantages over local builds
- Set up and configure EAS Build for your project
- Create and manage build profiles for different environments
- Build iOS apps for simulators, devices, and the App Store
- Build Android apps as APKs and AABs for distribution
- Troubleshoot common build issues
What is EAS Build?
EAS Build is Expo's cloud-based build service that compiles your React Native app into native iOS and Android binaries. It eliminates the need for local build environments, making it possible to build iOS apps without a Mac.
Local Builds vs EAS Build
flowchart TB
subgraph Local["Local Builds"]
L1[Requires Xcode for iOS]
L2[Requires Android Studio]
L3[Manual dependency management]
L4[Machine-specific issues]
L5[Limited to your hardware]
end
subgraph EAS["EAS Build"]
E1[No local setup required]
E2[Consistent build environment]
E3[Build iOS on any OS]
E4[Automatic credential management]
E5[Parallel builds]
E6[Build caching]
end
Local --> |Migration| EAS
style EAS fill:#e8f5e9,stroke:#4CAF50
style Local fill:#fff3e0,stroke:#ff9800
How EAS Build Works
sequenceDiagram
participant Dev as Developer
participant CLI as EAS CLI
participant Cloud as EAS Cloud
participant Store as App Stores
Dev->>CLI: eas build
CLI->>Cloud: Upload source code
Cloud->>Cloud: Install dependencies
Cloud->>Cloud: Run prebuild (if needed)
Cloud->>Cloud: Compile native code
Cloud->>Cloud: Sign app
Cloud-->>Dev: Build complete notification
Dev->>Cloud: Download build
Dev->>Store: Submit to stores
EAS Build Pricing
| Plan | Builds/Month | Priority | Features |
|---|---|---|---|
| Free | 30 builds | Normal queue | Basic features |
| Production | Unlimited | Priority queue | Team features, larger caches |
| Enterprise | Unlimited | Dedicated queue | SSO, SLA, custom runners |
💡 When to Use Local Builds
Local builds are still useful for:
- Debugging native code issues
- Testing native modules in development
- Organizations with strict security requirements
- When you've exhausted your free tier
Setting Up EAS Build
Getting started with EAS Build requires a few setup steps.
Prerequisites
# Install or update EAS CLI globally
npm install -g eas-cli
# Verify installation
eas --version
# Login to your Expo account
eas login
# Verify login
eas whoami
Initialize EAS in Your Project
# Navigate to your project
cd my-app
# Initialize EAS configuration
eas build:configure
# This creates:
# - eas.json (build configuration)
# - Updates app.json with EAS project ID
Project Structure After Setup
my-app/
├── app.json # Updated with EAS project ID
├── eas.json # Build profiles and configuration
├── package.json
├── app/
│ └── ...
└── assets/
└── ...
Link to Expo Project
# If not already linked, connect to Expo project
eas init
# Or update existing project
eas project:init
# This adds the project ID to app.json:
{
"expo": {
"extra": {
"eas": {
"projectId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
}
}
Verify Configuration
# Check your EAS configuration
eas config
# Validate build configuration
eas build:inspect
# View project info
eas project:info
Build Profiles
Build profiles define different configurations for various stages of development and distribution.
Understanding Build Profiles
flowchart LR
subgraph Profiles["Build Profiles"]
D[development]
P[preview]
PR[production]
end
subgraph DevUse["Development"]
D1[Development client]
D2[Debug builds]
D3[Fast iteration]
end
subgraph PreviewUse["Preview/Staging"]
P1[Internal testing]
P2[Stakeholder review]
P3[QA testing]
end
subgraph ProdUse["Production"]
PR1[App Store]
PR2[Play Store]
PR3[End users]
end
D --> DevUse
P --> PreviewUse
PR --> ProdUse
style D fill:#e3f2fd
style P fill:#fff3e0
style PR fill:#e8f5e9
Default Build Profiles
// eas.json - created by eas build:configure
{
"cli": {
"version": ">= 7.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal"
},
"production": {}
},
"submit": {
"production": {}
}
}
Profile Properties Explained
| Property | Description | Values |
|---|---|---|
developmentClient |
Include Expo dev tools | true/false |
distribution |
How the build will be distributed | internal, store, simulator |
channel |
OTA update channel | Any string (e.g., "production") |
autoIncrement |
Auto-increment build number | true/false, "version", "buildNumber" |
env |
Environment variables | Object of key-value pairs |
Comprehensive Build Profiles
// eas.json - Full configuration example
{
"cli": {
"version": ">= 7.0.0",
"appVersionSource": "remote"
},
"build": {
"base": {
"node": "20.11.0",
"env": {
"EXPO_PUBLIC_ENVIRONMENT": "production"
}
},
"development": {
"extends": "base",
"developmentClient": true,
"distribution": "internal",
"env": {
"EXPO_PUBLIC_ENVIRONMENT": "development"
},
"ios": {
"simulator": true,
"resourceClass": "m-medium"
},
"android": {
"buildType": "apk",
"gradleCommand": ":app:assembleDebug"
}
},
"development-device": {
"extends": "development",
"ios": {
"simulator": false
}
},
"preview": {
"extends": "base",
"distribution": "internal",
"channel": "preview",
"env": {
"EXPO_PUBLIC_ENVIRONMENT": "staging"
},
"ios": {
"resourceClass": "m-medium"
},
"android": {
"buildType": "apk"
}
},
"production": {
"extends": "base",
"channel": "production",
"autoIncrement": true,
"ios": {
"resourceClass": "large"
},
"android": {
"buildType": "app-bundle"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "your@email.com",
"ascAppId": "1234567890",
"appleTeamId": "XXXXXXXXXX"
},
"android": {
"serviceAccountKeyPath": "./play-store-key.json",
"track": "internal"
}
}
}
}
Profile Inheritance with extends
// Use "extends" to inherit from other profiles
{
"build": {
"base": {
"node": "20.11.0",
"cache": {
"key": "custom-cache-key"
}
},
"development": {
"extends": "base",
"developmentClient": true
// Inherits node version and cache from base
},
"production": {
"extends": "base",
"autoIncrement": true
// Also inherits from base
}
}
}
The eas.json Configuration
Let's explore all the configuration options available in eas.json.
Top-Level Structure
{
"cli": {
// CLI version requirements and settings
},
"build": {
// Build profile configurations
},
"submit": {
// App store submission configurations
}
}
CLI Configuration
{
"cli": {
// Minimum EAS CLI version required
"version": ">= 7.0.0",
// Where to get app version (local or remote)
"appVersionSource": "remote",
// Prompt to update CLI if outdated
"promptToConfigurePushNotifications": false
}
}
Build Configuration Options
{
"build": {
"production": {
// === Distribution ===
"distribution": "store", // "internal", "store", or "simulator"
// === Development ===
"developmentClient": false,
// === Versioning ===
"autoIncrement": true, // or "version", "buildNumber"
// === Updates ===
"channel": "production",
// === Environment ===
"env": {
"EXPO_PUBLIC_API_URL": "https://api.production.com"
},
// === Node/Runtime ===
"node": "20.11.0",
"yarn": "1.22.19",
"pnpm": "8.14.0",
"bun": "1.0.25",
// === Caching ===
"cache": {
"key": "production-v1",
"cacheDefaultPaths": true,
"customPaths": ["./custom-cache-dir"]
},
// === Build Steps ===
"prebuildCommand": "npm run prebuild-script",
// === Credentials ===
"credentialsSource": "remote", // "local" or "remote"
// === iOS Specific ===
"ios": {
"simulator": false,
"enterpriseProvisioning": "universal", // or "adhoc"
"autoIncrement": "buildNumber",
"image": "latest",
"resourceClass": "large", // "m-medium", "m-large", "large"
"bundleIdentifier": "com.company.app"
},
// === Android Specific ===
"android": {
"buildType": "app-bundle", // "apk" or "app-bundle"
"gradleCommand": ":app:bundleRelease",
"autoIncrement": "versionCode",
"image": "latest",
"resourceClass": "large",
"applicationId": "com.company.app",
"ndk": "25.1.8937393"
}
}
}
}
Resource Classes
| Class | CPU | Memory | Use Case |
|---|---|---|---|
| default (free) | 2 cores | 4 GB | Simple apps |
| m-medium | 4 cores | 12 GB | Most apps |
| m-large | 8 cores | 16 GB | Large apps |
| large | 12 cores | 32 GB | Complex apps with many native deps |
Caching Configuration
{
"build": {
"production": {
"cache": {
// Unique cache key - change to invalidate cache
"key": "production-v1",
// Cache default paths (node_modules, etc.)
"cacheDefaultPaths": true,
// Additional paths to cache
"customPaths": [
"./android/.gradle",
"./ios/Pods"
],
// Disable caching entirely
"disabled": false
}
}
}
}
// To invalidate cache, either:
// 1. Change the cache key
// 2. Run with --clear-cache flag
eas build --platform ios --profile production --clear-cache
iOS Builds
Building for iOS requires code signing credentials. EAS can manage these automatically or you can provide your own.
iOS Build Types
flowchart TB
subgraph Types["iOS Build Types"]
SIM[Simulator Build]
DEV[Development Build]
ADHOC[Ad Hoc Build]
STORE[App Store Build]
end
subgraph Use["Use Cases"]
SIM --> U1[Local testing on Mac]
DEV --> U2[Physical device with dev tools]
ADHOC --> U3[Internal testing up to 100 devices]
STORE --> U4[TestFlight and App Store]
end
style SIM fill:#e3f2fd
style DEV fill:#fff3e0
style ADHOC fill:#e8f5e9
style STORE fill:#f3e5f5
Building for iOS Simulator
# Build for iOS simulator (no credentials needed)
eas build --platform ios --profile development
# eas.json configuration
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
}
}
}
# The build produces a .app file that runs on simulators only
Building for Physical Devices
# Build for physical device testing
eas build --platform ios --profile development-device
# eas.json configuration
{
"build": {
"development-device": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": false
}
}
}
}
# First build will prompt for:
# 1. Apple Developer account login
# 2. Team selection (if multiple teams)
# 3. Device registration
Credential Management
# View current credentials
eas credentials
# Select iOS credentials
eas credentials --platform ios
# Options:
# 1. Let EAS manage credentials (recommended)
# 2. Use existing credentials from your machine
# 3. Provide credentials manually
# Clear and regenerate credentials
eas credentials --platform ios
# Then select "Remove credentials" and rebuild
💡 Credential Types
- Distribution Certificate: Signs your app (limited to 3 per team)
- Provisioning Profile: Links certificate to app ID and devices
- Push Notification Key: Enables push notifications
Registering Test Devices
# Register a device for internal builds
eas device:create
# Options:
# 1. Website - generates QR code for device to scan
# 2. Manual - enter UDID directly
# View registered devices
eas device:list
# Delete a device
eas device:delete
# After adding devices, rebuild to include them
# in the provisioning profile
Building for App Store
# Build for App Store / TestFlight
eas build --platform ios --profile production
# eas.json configuration
{
"build": {
"production": {
"distribution": "store",
"ios": {
"autoIncrement": "buildNumber"
}
}
}
}
# The build produces an .ipa file suitable for App Store submission
iOS Build Commands
# Start an iOS build
eas build --platform ios --profile production
# Build with specific message
eas build --platform ios --profile production --message "Release v1.2.0"
# View build status
eas build:list --platform ios
# View build details
eas build:view [BUILD_ID]
# Download a build
eas build:download [BUILD_ID]
# Cancel a running build
eas build:cancel [BUILD_ID]
# View build logs
eas build:view [BUILD_ID] --logs
iOS-Specific Configuration
{
"build": {
"production": {
"ios": {
// Build number auto-increment
"autoIncrement": "buildNumber",
// Simulator or device
"simulator": false,
// Build image (Xcode version)
"image": "latest", // or "macos-ventura-13.6-xcode-15.2"
// Resource class for faster builds
"resourceClass": "large",
// Override bundle identifier
"bundleIdentifier": "com.company.app",
// Enterprise provisioning
"enterpriseProvisioning": "universal", // or "adhoc"
// Scheme to build (for bare workflow)
"scheme": "MyApp"
}
}
}
}
Android Builds
Android builds can produce APKs for direct installation or AABs for Google Play.
APK vs AAB
| Format | Full Name | Use Case | Installation |
|---|---|---|---|
| APK | Android Package | Internal testing, direct install | Direct install on device |
| AAB | Android App Bundle | Google Play distribution | Via Play Store only |
✅ AAB Benefits
- Smaller download size (20-50% smaller)
- Dynamic delivery of features
- Required by Google Play since August 2021
- Automatic optimization per device
Building APK for Testing
# Build APK for internal testing
eas build --platform android --profile preview
# eas.json configuration
{
"build": {
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
}
}
}
# Install APK on device
adb install my-app.apk
# Or scan QR code from EAS build page
Building AAB for Play Store
# Build AAB for Google Play
eas build --platform android --profile production
# eas.json configuration
{
"build": {
"production": {
"distribution": "store",
"android": {
"buildType": "app-bundle"
}
}
}
}
Android Signing
# Android uses keystore files for signing
# EAS can manage these automatically
# View/manage Android credentials
eas credentials --platform android
# Options:
# 1. Generate new keystore (EAS managed)
# 2. Upload existing keystore
# 3. Use credentials.json for CI
# Keystore is stored encrypted on EAS servers
# Download backup of keystore (important!)
eas credentials --platform android
# Select "Download keystore"
⚠️ Keystore Backup
CRITICAL: Back up your production keystore immediately after creation! If you lose it, you cannot update your app on Google Play.
# Download and securely store your keystore
eas credentials --platform android
# Select your project
# Select "Keystore" > "Download keystore"
# Store the keystore and these values securely:
# - Keystore file (.jks or .keystore)
# - Keystore password
# - Key alias
# - Key password
Android Build Commands
# Start Android build
eas build --platform android --profile production
# Build both APK and AAB
eas build --platform android --profile preview # APK
eas build --platform android --profile production # AAB
# Build for all platforms at once
eas build --platform all --profile production
# View build status
eas build:list --platform android
# Download build
eas build:download [BUILD_ID]
Android-Specific Configuration
{
"build": {
"production": {
"android": {
// Build type
"buildType": "app-bundle", // or "apk"
// Custom Gradle command
"gradleCommand": ":app:bundleRelease",
// Version code auto-increment
"autoIncrement": "versionCode",
// Build image
"image": "latest", // or "ubuntu-22.04-jdk-17-ndk-r25c"
// Resource class
"resourceClass": "large",
// Override package name
"applicationId": "com.company.app",
// NDK version
"ndk": "25.1.8937393",
// Gradle properties
"config": {
"googleMaps": {
"apiKey": "AIza..."
}
}
}
}
}
}
Building for Both Platforms
# Build iOS and Android simultaneously
eas build --platform all --profile production
# Build with same profile
eas build --platform all --profile preview
# Check status of all builds
eas build:list
# Note: iOS and Android builds run in parallel
# Each platform has its own queue
Advanced Configuration
Advanced options for customizing your build process.
Custom Build Scripts
// eas.json
{
"build": {
"production": {
// Run before native build starts
"prebuildCommand": "npm run prepare-build",
// Custom npm/yarn command
"buildCommand": "npm run custom-build"
}
}
}
// package.json
{
"scripts": {
"prepare-build": "node scripts/prepare.js",
"custom-build": "expo prebuild && npm run post-prebuild"
}
}
// scripts/prepare.js
const fs = require('fs');
// Generate build-specific files
const buildInfo = {
buildTime: new Date().toISOString(),
gitCommit: process.env.EAS_BUILD_GIT_COMMIT_HASH,
buildNumber: process.env.EAS_BUILD_NUMBER
};
fs.writeFileSync('./build-info.json', JSON.stringify(buildInfo, null, 2));
Using Build Environment Variables
// EAS provides these environment variables during build:
// Build info
process.env.EAS_BUILD_ID // Unique build ID
process.env.EAS_BUILD_PROFILE // Current profile name
process.env.EAS_BUILD_GIT_COMMIT_HASH // Git commit
process.env.EAS_BUILD_GIT_BRANCH // Git branch
process.env.EAS_BUILD_PLATFORM // "android" or "ios"
process.env.EAS_BUILD_RUNNER_OS // "darwin" or "linux"
// Version info
process.env.EAS_BUILD_APP_VERSION // From app config
process.env.EAS_BUILD_NUMBER // Build number
// Usage in app.config.js
export default {
expo: {
extra: {
buildId: process.env.EAS_BUILD_ID,
buildProfile: process.env.EAS_BUILD_PROFILE,
gitCommit: process.env.EAS_BUILD_GIT_COMMIT_HASH?.substring(0, 7)
}
}
};
Monorepo Configuration
// For monorepo setups with multiple apps
// eas.json in each app directory
{
"cli": {
"version": ">= 7.0.0"
},
"build": {
"production": {
// Specify project root relative to repo root
"config": "eas.json"
}
}
}
// Project structure
my-monorepo/
├── apps/
│ ├── mobile-app/
│ │ ├── app.json
│ │ ├── eas.json
│ │ └── package.json
│ └── admin-app/
│ ├── app.json
│ ├── eas.json
│ └── package.json
├── packages/
│ └── shared/
└── package.json
// Build from app directory
cd apps/mobile-app
eas build --platform all --profile production
Custom Build Images
// Specify exact build environment
{
"build": {
"production": {
"ios": {
// Use specific Xcode version
"image": "macos-ventura-13.6-xcode-15.2"
},
"android": {
// Use specific Java/NDK version
"image": "ubuntu-22.04-jdk-17-ndk-r25c"
}
}
}
}
// Check available images
// https://docs.expo.dev/build-reference/infrastructure/
Secrets and Credentials in CI/CD
// For CI/CD pipelines, use credentials.json
// 1. Download credentials
eas credentials --platform ios
// Select "Download credentials"
// 2. Create credentials.json (add to .gitignore!)
{
"ios": {
"MyApp": {
"distributionCertificate": {
"path": "./certs/dist.p12",
"password": "cert-password"
},
"provisioningProfilePath": "./certs/profile.mobileprovision"
}
},
"android": {
"keystore": {
"keystorePath": "./certs/keystore.jks",
"keystorePassword": "keystore-password",
"keyAlias": "key-alias",
"keyPassword": "key-password"
}
}
}
// 3. Use in eas.json
{
"build": {
"production": {
"credentialsSource": "local"
}
}
}
// 4. In CI, decode base64 secrets to create files
# GitHub Actions example
- name: Setup credentials
run: |
echo "${{ secrets.IOS_DIST_CERT }}" | base64 -d > certs/dist.p12
echo "${{ secrets.IOS_PROVISION }}" | base64 -d > certs/profile.mobileprovision
echo "${{ secrets.CREDENTIALS_JSON }}" | base64 -d > credentials.json
Build Hooks
// eas-build-pre-install.sh - runs before npm install
#!/bin/bash
echo "Pre-install hook running..."
# Setup custom dependencies, configure environment, etc.
// eas-build-post-install.sh - runs after npm install
#!/bin/bash
echo "Post-install hook running..."
# Patch native modules, generate files, etc.
// eas-build-on-success.sh - runs after successful build
#!/bin/bash
echo "Build succeeded!"
# Notify team, trigger deployments, etc.
// eas-build-on-error.sh - runs after failed build
#!/bin/bash
echo "Build failed!"
# Send alerts, capture logs, etc.
// Add scripts to your project root
// They must be executable: chmod +x eas-build-*.sh
Troubleshooting
Common build issues and how to resolve them.
Common iOS Build Errors
❌ "No matching provisioning profile"
# Cause: Provisioning profile doesn't match app or devices
# Solutions:
# 1. Regenerate credentials
eas credentials --platform ios
# Select "Build credentials" > "Remove credentials"
# Then rebuild - new credentials will be created
# 2. Ensure devices are registered
eas device:list
# Add missing devices and rebuild
# 3. Check bundle identifier matches
# app.json ios.bundleIdentifier must match credentials
❌ "Code signing is required for product type"
# Cause: Missing or invalid distribution certificate
# Solutions:
# 1. Let EAS regenerate certificate
eas credentials --platform ios
# Select "Distribution Certificate" > "Create new"
# 2. Check certificate hasn't expired
# Apple certificates expire after 1 year
# 3. For Enterprise accounts, ensure correct provisioning type
❌ "Pod install failed"
# Cause: CocoaPods dependency issues
# Solutions:
# 1. Clear cache and rebuild
eas build --platform ios --profile production --clear-cache
# 2. Update pods locally first
cd ios && pod install --repo-update
# 3. Check for conflicting pod versions
# Review Podfile.lock for version conflicts
# 4. Specify cocoapods version
{
"build": {
"production": {
"ios": {
"cocoapods": "1.14.3"
}
}
}
}
Common Android Build Errors
❌ "Execution failed for task ':app:mergeReleaseResources'"
# Cause: Resource conflicts or invalid assets
# Solutions:
# 1. Check for duplicate resources
# Look for files with same name in different res folders
# 2. Validate image assets
# Ensure no corrupt or invalid images in assets
# 3. Clear build cache
eas build --platform android --profile production --clear-cache
❌ "Out of memory error during build"
# Cause: Build requires more memory than available
# Solutions:
# 1. Use larger resource class
{
"build": {
"production": {
"android": {
"resourceClass": "large"
}
}
}
}
# 2. Increase Gradle memory
# android/gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g
# 3. Disable parallel builds if needed
org.gradle.parallel=false
❌ "Keystore was tampered with, or password was incorrect"
# Cause: Wrong keystore password or corrupted keystore
# Solutions:
# 1. Verify credentials
eas credentials --platform android
# 2. Re-upload correct keystore
eas credentials --platform android
# Select "Upload keystore"
# 3. If lost, for new apps only, create new keystore
# WARNING: Existing apps cannot update with new keystore!
General Troubleshooting Steps
# 1. Check build logs for errors
eas build:view [BUILD_ID] --logs
# 2. Clear all caches
eas build --platform all --profile production --clear-cache
# 3. Verify eas.json syntax
npx eas-cli config
# 4. Ensure latest EAS CLI
npm install -g eas-cli@latest
# 5. Check for known issues
# https://status.expo.dev/
# https://github.com/expo/expo/issues
# 6. Validate app.json / app.config.js
npx expo config --type public
# 7. Run prebuild locally to catch issues
npx expo prebuild --clean
# 8. Test local build (if possible)
npx expo run:ios
npx expo run:android
Debugging Failed Builds
# View full build logs
eas build:view [BUILD_ID]
# Download build artifacts for inspection
eas build:download [BUILD_ID]
# Check build metadata
eas build:inspect
# Review what files are being uploaded
eas build --platform ios --profile production --local
# Test configuration without building
eas build --platform ios --profile production --dry-run
Build Queue and Priority
# Check current queue status
eas build:list --status in-progress
eas build:list --status in-queue
# Free tier: Normal queue (can be 15-30 min during peak)
# Paid tier: Priority queue (usually < 5 min)
# Enterprise: Dedicated runners (immediate)
# Tips for faster builds:
# 1. Use caching effectively
# 2. Minimize native dependencies
# 3. Use appropriate resource class
# 4. Build during off-peak hours
# 5. Consider larger resource class for faster compilation
Hands-On Exercises
Exercise 1: Configure Complete Build Profiles
Set up a complete eas.json with development, preview, and production profiles for a team project.
Show Solution
// eas.json
{
"cli": {
"version": ">= 7.0.0",
"appVersionSource": "remote"
},
"build": {
"base": {
"node": "20.11.0",
"cache": {
"key": "v1",
"cacheDefaultPaths": true
}
},
"development": {
"extends": "base",
"developmentClient": true,
"distribution": "internal",
"channel": "development",
"env": {
"EXPO_PUBLIC_ENVIRONMENT": "development",
"EXPO_PUBLIC_API_URL": "https://api-dev.myapp.com"
},
"ios": {
"simulator": true,
"resourceClass": "m-medium"
},
"android": {
"buildType": "apk",
"gradleCommand": ":app:assembleDebug"
}
},
"development-device": {
"extends": "development",
"ios": {
"simulator": false
}
},
"preview": {
"extends": "base",
"distribution": "internal",
"channel": "preview",
"env": {
"EXPO_PUBLIC_ENVIRONMENT": "staging",
"EXPO_PUBLIC_API_URL": "https://api-staging.myapp.com"
},
"ios": {
"resourceClass": "m-medium"
},
"android": {
"buildType": "apk"
}
},
"production": {
"extends": "base",
"channel": "production",
"autoIncrement": true,
"env": {
"EXPO_PUBLIC_ENVIRONMENT": "production",
"EXPO_PUBLIC_API_URL": "https://api.myapp.com"
},
"ios": {
"resourceClass": "large",
"autoIncrement": "buildNumber"
},
"android": {
"buildType": "app-bundle",
"autoIncrement": "versionCode",
"resourceClass": "large"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "developer@company.com",
"ascAppId": "1234567890",
"appleTeamId": "XXXXXXXXXX"
},
"android": {
"serviceAccountKeyPath": "./google-play-key.json",
"track": "internal"
}
}
}
}
Exercise 2: Build and Distribute to Testers
Build an iOS and Android preview build and distribute to your test team.
Show Solution
# Step 1: Register test devices for iOS
eas device:create
# Have testers scan QR code with their iOS devices
# Step 2: Build preview for both platforms
eas build --platform all --profile preview
# Step 3: Wait for builds to complete
eas build:list --status in-progress
# Step 4: Once complete, share with testers
# Option A: Share build page URL
# Testers scan QR code to install
# Option B: Use internal distribution
eas build:list --profile preview
# Get the build URLs and share
# For Android:
# Testers can directly install the APK
# For iOS:
# Testers must be registered in the provisioning profile
# They install via the build page
# Step 5: Verify installation
# Have testers confirm app launches and shows
# correct environment (staging API)
Exercise 3: Troubleshoot a Failed Build
Practice diagnosing and fixing a build failure scenario.
Show Solution
# Scenario: Your iOS build failed with "pod install" error
# Step 1: Get build details
eas build:view [BUILD_ID]
# Step 2: Review logs for specific error
# Look for lines starting with "error:" or "fatal:"
# Step 3: Common fixes to try:
# Fix A: Clear cache
eas build --platform ios --profile production --clear-cache
# Fix B: Update pods locally
cd ios
pod install --repo-update
pod update
cd ..
# Fix C: Lock CocoaPods version
// eas.json
{
"build": {
"production": {
"ios": {
"cocoapods": "1.14.3"
}
}
}
}
# Fix D: Clean prebuild
npx expo prebuild --clean
# Fix E: Check for native module issues
# Review recently added packages
# Check compatibility with current Expo SDK
# Step 4: Test locally if possible
npx expo run:ios
# Step 5: If still failing, isolate the issue
# Create minimal reproduction
# Check Expo GitHub issues
# Ask in Expo Discord community
Summary
🎯 Key Takeaways
- EAS Build: Cloud-based builds eliminate local environment complexity
- Build profiles: Separate configurations for dev, preview, and production
- iOS builds: Simulator, device, and App Store with managed credentials
- Android builds: APK for testing, AAB for Play Store
- Credentials: Let EAS manage them, but always backup keystores
- Caching: Use cache keys to speed up builds
- Resource classes: Larger classes for faster, more reliable builds
- Troubleshooting: Check logs, clear cache, verify credentials
Build Command Quick Reference
# Essential commands
eas build:configure # Initial setup
eas build -p ios # Build iOS (default profile)
eas build -p android # Build Android
eas build -p all # Build both platforms
eas build -p ios --profile preview # Specific profile
# Credential management
eas credentials # Interactive credential manager
eas device:create # Register iOS test device
# Build management
eas build:list # View all builds
eas build:view [ID] # View build details
eas build:download [ID] # Download build artifact
eas build:cancel [ID] # Cancel running build
# Flags
--clear-cache # Clear build cache
--local # Run build locally
--message "..." # Add build message
--non-interactive # For CI/CD
In the next lesson, we'll learn about over-the-air updates with EAS Update, allowing you to push JavaScript updates without going through the app stores.