Android build local steps overview
The local phase of Android builds on EAS happens on your computer, where EAS CLI performs repository preparation, credential management, project archiving, and upload. The process checks git status if `cli.requireCommit` is true in eas.json, prepares credentials based on the credentialsSource setting, creates a tarball of the repository, and uploads it to a private GCS bucket before sending the build request to EAS Build.
Android build remote steps overview
The remote phase occurs on EAS Build servers and includes: creating a Docker container with build tools, downloading and unpacking the project tarball, creating .npmrc if NPM_TOKEN is set, running eas-build-pre-install script if defined, running npm or yarn install, running npx expo-doctor, running npx expo prebuild for CNG projects, restoring cache, running eas-build-post-install script, restoring keystore, injecting signing configuration into build.gradle, running ./gradlew COMMAND, running post-build scripts (eas-build-pre-upload-artifacts deprecated, eas-build-on-success, eas-build-on-error, eas-build-on-complete), and uploading artifacts to GCS.
Android SDK and build tools in Docker container
Every Android build on EAS Build gets its own fresh Docker container with all required build tools pre-installed, including Java JDK, Android SDK, NDK, and other necessary tools.
NPM_TOKEN environment variable creates .npmrc
If the `NPM_TOKEN` environment variable is set, EAS Build will create a .npmrc file during the remote build phase.
eas-build-pre-install and eas-build-post-install scripts
The eas-build-pre-install script from package.json runs after npm/yarn install, before cache restoration. The eas-build-post-install script runs after cache restoration and before keystore restoration. Both scripts are optional and only run if defined in package.json.
eas-build-on-success, eas-build-on-error, and eas-build-on-complete scripts
After the Gradle build completes, EAS Build runs optional scripts from package.json: eas-build-on-success if the build was successful, eas-build-on-error if the build failed, and eas-build-on-complete in both cases. The eas-build-on-complete script has access to the EAS_BUILD_STATUS environment variable which is set to either 'finished' or 'errored'.
eas-build-pre-upload-artifacts script is deprecated
The eas-build-pre-upload-artifacts script from package.json is deprecated and should no longer be used.
Android keystore security requirement
Your application's keystore should be kept private and must never be checked in to your repository. Debug keystores are the only exception because they are not used for uploading apps to the Google Play Store.
Google Play Store app identification by certificate
The Google Play Store identifies applications based on the certificate stored in the keystore. If you lose your keystore, you may not be able to update your application in the store. However, you can mitigate this risk by using Play App Signing.
eas-build.gradle file location and creation
EAS Build creates the file `android/app/eas-build.gradle` which contains the signing configuration for Gradle. This file is imported in `android/app/build.gradle` using `apply from: "./eas-build.gradle"`.
eas-build.gradle signing configuration details
The eas-build.gradle file defines a `release` signing config that reads keystore and passwords from the credentials.json file at the project root. The config includes: storeFile (path resolved from keystorePath), storePassword, keyAlias, and keyPassword. If keyPassword is not present in credentials for PKCS keystores, the keystorePassword is used instead. The release signing config is applied to both android.buildTypes.release and android.buildTypes.debug.
VCS workflow affects tarball creation
The creation of the project tarball during the local build phase depends on the VCS workflow being used.
Disable Maven cache server with EAS_BUILD_DISABLE_MAVEN_CACHE
To disable the Maven cache server, set the EAS_BUILD_DISABLE_MAVEN_CACHE environment variable to "1" in eas.json. If you use EAS Workflows, set it in a job's env instead. EAS Build runs a Maven cache server that caches maven-central (https://repo1.maven.org/maven2/), google (https://maven.google.com/), and plugins (https://plugins.gradle.org/m2/).
Android build VM isolation
Android builders run on virtual machines in an isolated environment. Every build gets its own dedicated VM instance.
Android build environment configuration
Android builds include npm cache and Maven cache deployed with Kubernetes. Gradle JVM args are injected via the GRADLE_OPTS environment variable. Global npm configuration uses registry=http://npm.production.caches.eas-build.internal. Global Yarn configuration in ~/.yarnrc.yml sets npmRegistryServer to http://npm.production.caches.eas-build.internal, unsafeHttpWhitelist to '*', and enableImmutableInstalls to false.
Gradle JVM heap size by resource class
The GRADLE_OPTS environment variable sets maximum heap size (-Xmx) based on resource class: 'medium' resource class uses -Xmx4g, 'large' resource class uses -Xmx8g.
Additional Gradle JVM arguments injected by worker
In addition to -Xmx, the worker passes these JVM args to Gradle via -Dorg.gradle.jvmargs: -XX:MaxMetaspaceSize=1g, -XX:+HeapDumpOnOutOfMemoryError, and -Dfile.encoding=UTF-8. The worker also sets these top-level Gradle properties on GRADLE_OPTS: -Dorg.gradle.parallel=true and -Dorg.gradle.daemon=false.
Ubuntu 26.04 JDK 17 NDK r27b SDK 56 Android image
Image name: ubuntu-26.04-jdk-17-ndk-r27b (alias: sdk-56). GCE image: ubuntu-2604-resolute-amd64-v20260505. NDK 27.1.12297006, Node.js 22.22.2, Bun 1.3.13, Yarn 1.22.22, pnpm 10.33.3, npm 10.9.4, Java 17, node-gyp 12.3.0, Maestro 2.5.1.
Ubuntu 24.04 JDK 17 NDK r27b SDK 54 Android image
Image name: ubuntu-24.04-jdk-17-ndk-r27b (alias: sdk-54). GCE image: ubuntu-2404-noble-amd64-v20250805. NDK 27.1.12297006, Node.js 20.19.4, Bun 1.2.20, Yarn 1.22.22, pnpm 10.14.0, npm 10.9.3, Java 17, node-gyp 11.3.0, Maestro 2.0.2.
Ubuntu 22.04 JDK 17 NDK r26b legacy SDK 51 SDK 52 Android images
Image name: ubuntu-22.04-jdk-17-ndk-r26b-like (aliases: sdk-51, sdk-52). Docker image: ubuntu:jammy-v20250112. NDK 26.1.10909125, Node.js 20.18.3, Bun 1.2.4, Yarn 1.22.22, pnpm 9.15.5, npm 10.8.2, Java 17, node-gyp 11.1.0.
Ubuntu 22.04 JDK 17 NDK r25b SDK 50 Android image
Image name: ubuntu-22.04-jdk-17-ndk-r25b (alias: sdk-50). Docker image: ubuntu:jammy-20220810. NDK 25.1.8937393, Node.js 18.18.0, Bun 1.0.14, Yarn 1.22.19, pnpm 8.9.2, npm 9.8.1, Java 17, node-gyp 10.0.1.
Ubuntu 22.04 JDK 11 NDK r23b SDK 49 Android image
Image name: ubuntu-22.04-jdk-11-ndk-r23b (alias: sdk-49). Docker image: ubuntu:jammy-20220810. NDK 23.1.7779620, Node.js 18.18.0, Bun 1.0.14, Yarn 1.22.19, pnpm 8.7.5, npm 9.8.1, Java 11, node-gyp 10.0.1.
Ubuntu 22.04 JDK 8 NDK r21e Android image (deprecated)
Image name: ubuntu-22.04-jdk-8-ndk-r21e (deprecated). Docker image: ubuntu:jammy-20220810. NDK 21.4.7075529, Node.js 18.18.0, Bun 1.0.14, Yarn 1.22.19, pnpm 7.0.0, npm 9.8.1, Java 8, node-gyp 10.0.1.
Ubuntu 20.04 JDK 8 NDK r19c Android image (deprecated)
Image name: ubuntu-20.04-jdk-8-ndk-r19c (deprecated). Docker image: ubuntu:focal-20220823. NDK 19.2.5345600, Node.js 18.18.0, Bun 1.0.14, Yarn 1.22.19, pnpm 7.0.0, npm 9.8.1, Java 8, node-gyp 10.0.1.
Repack Android command example
$ npx @expo/repack-app --platform android --source-app MyApp.apk
Repack Android signing command example
$ npx @expo/repack-app --platform android --source-app MyApp.apk --ks keystore.jks --ks-key-alias my-alias
Build for Android with eas build --platform android
To create a build for the Google Play Store, run the command eas build --platform android. You can optionally attach a message with --message flag, for example: eas build --platform android --message "Some message".
eas/configure_android_version is not mandatory
It is not mandatory to use eas/configure_android_version. If not used, the version from native code generated during the prebuild phase will be used.
eas/start_android_emulator step requirements and inputs
The eas/start_android_emulator step starts an Android Emulator for testing. It is only available when running a build for Android and requires your project to be configured to use the old Build Infrastructure. Input parameters are: device_name (string, optional) for naming the created device, and system_image_package (string, optional) specifying the Android package path such as 'system-images;android-30;default;x86_64'. VMs run on x86_64 architecture, so always choose x86_64 package variants. Use 'sdkmanager --list' to get available system images. Source code is available at https://github.com/expo/eas-cli/blob/main/packages/build-tools/src/steps/functions/startAndroidEmulator.ts
Example: development build Android configuration
A simple internal distribution Android build can be configured with these steps in sequence: eas/checkout, eas/install_node_modules, eas/prebuild, eas/inject_android_credentials, eas/run_gradle, eas/find_and_upload_build_artifacts.
Example: production build Android for Google Play Store
A customized Android Play Store build uses these steps: eas/checkout, eas/install_node_modules, eas/prebuild, eas/inject_android_credentials, eas/run_gradle, eas/find_and_upload_build_artifacts.
Google Play Store requires AAB format for new apps
On Android, all new apps submitted to the Play Store must be built as an Android App Bundle (.aab) format.
AAB is processed by store into device-specific APKs
Android App Bundles (.aab) are uploaded to the Google Play Store, where the store generates a custom APK binary for each user based on their device's needs. The download size on the Play Store is the size of the APK generated from your AAB, tailored to the user's device.
APK download size vs actual Play Store size differs
The APK file size is typically much larger than what users download from the app store. The actual download size on the Play Store is smaller because the store generates device-specific APKs from your AAB.
Check Android app download size on Play Console
Google Play provides estimates for expected download size on your developer dashboard under the App size page in Android vitals on the Google Play Developer Console. This is a reliable estimate of the final app size that will be shipped to users.
React Native 0.73 changed extractNativeLibs default to false
React Native 0.73 bumped the Android minSdkVersion to 23, which changed the default value of extractNativeLibs to false. When set to false, native libraries are stored uncompressed in the APK, making the APK larger but allowing faster application load because libraries load directly from the APK at runtime.
React Native 0.73 APK and AAB size changes example
When upgrading to React Native 0.73, APK sizes increased significantly (SDK 49 release: 27.6 MB to SDK 50 release: 62.1 MB), but AAB and Google Play store download sizes remained relatively stable (SDK 49: 28.2 MB AAB, 11.7 MB Google Play; SDK 50: 27.4 MB AAB, 11.7 MB Google Play).
Revert extractNativeLibs behavior with useLegacyPackaging
To revert to the previous behavior before React Native 0.73, you can set useLegacyPackaging to true in your gradle.properties file or by using expo-build-properties.
Inspect Android APK contents with tools
To examine what assets are included in an Android build artifact, use Android APK Analyzer or apktool to inspect the contents of your app.
EAS Build Android-specific options
Android-specific options are available for EAS Build configuration, including image, env, distribution, withoutCredentials, and buildType properties.
Change Android flavor app name with strings.xml
To change the app name for a specific Android flavor, create android/app/src/{flavor}/res/value/strings.xml with the desired app name. For example, for the development flavor, create android/app/src/development/res/value/strings.xml with <string name="app_name">MyApp - Dev</string>.
Change Android flavor app icon with mipmap directories
To change the icon for a specific Android flavor, create android/app/src/{flavor}/res/mipmap-* directories with appropriate assets. Copy assets from android/app/src/main/res and replace the icon files.
Specify google-services.json for Android flavor
To specify google-services.json for a specific Android flavor, place it in android/app/src/{flavor}/google-services.json.
Package managers for creating Expo SDK projects
Projects can be bootstrapped using npm, yarn, pnpm, or bun. The commands are: npm: 'npx create-expo-app --template blank@57', yarn: 'yarn create expo-app --template blank@57', pnpm: 'pnpm create expo-app --template blank@57', bun: 'bun create expo --template blank@57'.
Android build local steps in EAS Build
On your local machine, EAS CLI completes the following steps before uploading to EAS Build: 1) If cli.requireCommit is set to true in eas.json, check if the git index is clean and provide an option to commit changes or abort. 2) Prepare credentials for the build unless builds.android.PROFILE_NAME.withoutCredentials is set to true. Credentials are obtained from either the local credentials.json file or from EAS servers based on the builds.android.PROFILE_NAME.credentialsSource value. If remote mode is selected but no credentials exist, you are prompted to generate a new keystore. 3) Create a tarball containing a copy of the repository. 4) Upload the project tarball to a private Google Cloud Storage bucket and send the build request to EAS Build.
Android build remote steps in EAS Build
When EAS Build picks up your request, the following steps occur on the remote service: 1) Create a new Docker container for the build with all build tools installed (Java JDK, Android SDK, NDK, etc.). 2) Download the project tarball from a private GCS bucket and unpack it. 3) Create .npmrc if NPM_TOKEN is set. 4) Run the eas-build-pre-install script from package.json if defined. 5) Run npm install in the project root (or yarn install if yarn.lock exists). 6) Run npx expo-doctor to diagnose potential configuration issues. 7) For CNG projects: Run npx expo prebuild to generate android and ios directories using versioned Expo CLI. 8) Restore a previously saved cache identified by the cache.key value in the build profile. 9) Run the eas-build-post-install script from package.json if defined. 10) Restore the keystore if included in the build request. 11) Inject the signing configuration into build.gradle. 12) Run ./gradlew COMMAND in the android directory, where COMMAND is defined at builds.android.PROFILE_NAME.gradleCommand (defaults to :app:bundleRelease for AAB). 13) Deprecated: Run the eas-build-pre-upload-artifacts script from package.json if defined. 14) Store a cache of files and directories defined in the build profile. 15) Upload the application archive to GCS with path configured at builds.android.PROFILE_NAME.applicationArchivePath (defaults to android/app/build/outputs/**/*.{apk,aab} using glob patterns). 16) Run eas-build-on-success script if build succeeds, or eas-build-on-error script if it fails. 17) Run eas-build-on-complete script with EAS_BUILD_STATUS env variable set to finished or errored. 18) Upload build artifacts archive to a private GCS bucket if buildArtifactPaths is specified in the build profile.
Android keystore security and Play App Signing
Android requires you to sign your application with a certificate stored in a keystore. The Google Play Store identifies applications based on the certificate, meaning if you lose your keystore, you may not be able to update your application in the store. However, you can mitigate this risk using Play App Signing. Your application's keystore should be kept private and never checked into your repository. Debug keystores are the only exception since they are not used for uploading apps to the Google Play Store.
How EAS Build injects Gradle signing configuration
EAS Build creates the file android/app/eas-build.gradle with signing configuration that reads keystore and passwords from the credentials.json file at the project root. This file configures the release signing config with storeFile, storePassword, keyAlias, and keyPassword values from credentials.android.keystore. If keyPassword is not present in credentials, the keystorePassword is used instead since key password is required by Gradle but PKCS keystores do not have one. The android/app/build.gradle file imports this configuration with the line: apply from: './eas-build.gradle'
Android build lifecycle scripts in package.json
EAS Build supports the following lifecycle scripts in package.json for Android builds: eas-build-pre-install (runs before npm install), eas-build-post-install (runs after dependencies are installed), eas-build-pre-upload-artifacts (deprecated, runs before uploading artifacts), eas-build-on-success (runs if build succeeds), eas-build-on-error (runs if build fails), and eas-build-on-complete (always runs with EAS_BUILD_STATUS env variable set to either finished or errored).
Android build caching in EAS Build
EAS Build supports caching of files and directories for Android builds. The cache is identified by the cache.key value specified in the build profile. Cached files and directories are restored at the beginning of the build and stored again after the build completes. Subsequent builds will restore this cache.
Android build skipping credentials in EAS Build
You can skip credential preparation for an Android build by setting builds.android.PROFILE_NAME.withoutCredentials to true in eas.json.
VCS workflow in EAS Build Android builds
The actual behavior of creating a tarball containing a copy of the repository depends on the VCS workflow you are using in EAS Build.
EAS Build Maven cache server for Android
EAS Build runs a Maven cache server that speeds up downloading Android dependencies. The cache covers maven-central (https://repo1.maven.org/maven2/), google (https://maven.google.com/), and plugins (https://plugins.gradle.org/m2/). To disable the Maven cache server, set the EAS_BUILD_DISABLE_MAVEN_CACHE environment variable to "1" in eas.json.
Android build server resource allocation
Android builders run on virtual machines in an isolated environment. Every build gets its own dedicated VM instance. The Gradle JVM args are injected via the GRADLE_OPTS environment variable when the build environment is provisioned. The resource class determines the max heap (-Xmx): 'medium' gets 4g, 'large' gets 8g. In addition to -Xmx, the worker passes -XX:MaxMetaspaceSize=1g, -XX:+HeapDumpOnOutOfMemoryError, and -Dfile.encoding=UTF-8 to the Gradle build JVM via -Dorg.gradle.jvmargs. The worker also sets -Dorg.gradle.parallel=true and -Dorg.gradle.daemon=false as top-level Gradle properties on GRADLE_OPTS.
Android GRADLE_OPTS default values by resource class
The GRADLE_OPTS environment variable is set on Android build VMs based on resource class. Resource class 'medium' has -Xmx set to 4g. Resource class 'large' has -Xmx set to 8g. Both classes also receive -XX:MaxMetaspaceSize=1g, -XX:+HeapDumpOnOutOfMemoryError, -Dfile.encoding=UTF-8, -Dorg.gradle.parallel=true, and -Dorg.gradle.daemon=false.
Overriding GRADLE_OPTS in eas.json
The worker sets org.gradle.jvmargs via GRADLE_OPTS, which overrides any org.gradle.jvmargs defined in your project's gradle.properties. You can replace the worker default by setting GRADLE_OPTS under a build profile's 'env' in eas.json, in a workflow file, or with EAS Environment Variables. Project environment values take precedence over the worker's default values.
Android global npm configuration
The global npm configuration on Android build servers in ~/.npmrc is: registry=http://npm.production.caches.eas-build.internal
Android global Yarn configuration
The global Yarn configuration on Android build servers in ~/.yarnrc.yml is: unsafeHttpWhitelist with '*', npmRegistryServer set to 'http://npm.production.caches.eas-build.internal', and enableImmutableInstalls set to false.
Android build server image ubuntu-26.04-jdk-17-ndk-r27b
The 'ubuntu-26.04-jdk-17-ndk-r27b' image is marked as 'sdk-56' alias. It uses GCE image 'ubuntu-2604-resolute-amd64-v20260505' with NDK 27.1.12297006, Node.js 22.22.2, Bun 1.3.13, Yarn 1.22.22, pnpm 10.33.3, npm 10.9.4, Java 17, node-gyp 12.3.0, and Maestro 2.5.1.
Android build server image ubuntu-24.04-jdk-17-ndk-r27b
The 'ubuntu-24.04-jdk-17-ndk-r27b' image is marked as 'sdk-54' alias. It uses GCE image 'ubuntu-2404-noble-amd64-v20250805' with NDK 27.1.12297006, Node.js 20.19.4, Bun 1.2.20, Yarn 1.22.22, pnpm 10.14.0, npm 10.9.3, Java 17, node-gyp 11.3.0, and Maestro 2.0.2.