Skip to main content

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.