# docker-android, the Android build environment React Native projects run in CI

> react-native-community/docker-android is a Docker image that packages the Android SDK, NDK, Node, Watchman, CMake, Gradle and a JDK into one Ubuntu base so React Native builds run the same way on any CI machine. It suits teams tired of provisioning their own Android SDK, and its design choice of preloading the Gradle distribution into a cache directory is the part that changes how fast a container starts.

**react-native-community/docker-android** — Android Docker Image for React Native and common android development.

- Repository: https://github.com/react-native-community/docker-android
- Stars: 540 · Forks: 153
- Language: Dockerfile
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/react-native-community-docker-android

## Why a React Native Android image exists at all

The motivation line points at a proposal rather than a paragraph of explanation. The README says this is an implementation of a proposal in the react-native-community discussions-and-proposals repository, numbered 0036 and titled Official Docker.

That framing tells you the shape of the project. It is not a general purpose Android build image that happens to work with React Native. It is the community's answer to a specific problem: the Android toolchain is a large stack of separately versioned pieces, and every machine or CI runner that builds a React Native app has to assemble the same stack before it can compile anything. The image makes that stack a single artifact that can be pulled rather than constructed.

The README also traces the lineage. The original version is split from the react-native repo, where it lived as ContainerShip/Dockerfile.android-base, so the starting point was the base Android image the React Native project used for its own continuous integration, extracted and generalised.

For a reader deciding whether to use it, the practical question is whether the version pins match the React Native version you are on. The image targets React Native, not Gradle in general, and the version set it carries reflects what the React Native build expects.

There is also a working reference implementation. The README points to react-native-community/ci-sample as the reference example, which is the fastest way to see the image used the way its authors intended rather than in the abstract.

## Build arguments that pin the whole Android stack

The Dockerfile starts from ubuntu:22.04 and then fixes every moving part of the toolchain through build arguments, so a consumer can rebuild the image with different versions without editing the file.

```dockerfile
ARG SDK_VERSION=commandlinetools-linux-11076708_latest.zip
ARG ANDROID_BUILD_VERSION=37.0
ARG ANDROID_TOOLS_VERSION=37.0.0
ARG NDK_VERSION=28.2.13676358
ARG NODE_VERSION=22.14
ARG WATCHMAN_VERSION=4.9.0
ARG CMAKE_VERSION=3.30.5
ARG GRADLE_VERSION=9.4.1
```

The SDK argument points at the Android command line tools archive rather than a directory of individually installed components, and the comment above it links to the developer.android.com page for the command line tools. The Node, Watchman and CMake arguments cover the parts of a React Native build that are not Android specific at all: Watchman is the file watcher the Metro bundler uses, and CMake is needed by the NDK toolchain for native modules.

The platform version is where the file is most informative. The Dockerfile notes that Android 17, which is API 37, ships as a versioned platform package called platforms;android-37.0, following a new api.minor scheme, and that a plain android-37 does not exist. That single comment saves hours of confusion for anyone writing their own Gradle configuration against a platform that has moved to a two-part version.

The Gradle argument has a second role beyond the version, which the README explains separately.

## Environment variables that make the toolchain findable

The image sets the environment the way a local Android development machine would, so that Gradle, adb and the NDK resolve without extra configuration in the project.

```dockerfile
ENV ANDROID_HOME=/opt/android
ENV ANDROID_SDK_ROOT=${ANDROID_HOME}
ENV ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/$NDK_VERSION
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ENV CMAKE_BIN_PATH=${ANDROID_HOME}/cmake/$CMAKE_VERSION/bin
ENV GRADLE_USER_HOME=/opt/gradle-home
```

Both ANDROID_HOME and ANDROID_SDK_ROOT point at the same place, which is deliberate compatibility rather than redundancy, since tooling in the ecosystem still reads one or the other depending on its age. The NDK path is derived from the NDK version argument, so changing that argument moves the path with it. The JDK is pinned to OpenJDK 17 at a fixed system path, and the CMake path is likewise derived from its version argument.

The PATH line is where the pieces get collected. It prepends the CMake bin directory, the Android cmdline-tools latest bin, the emulator, platform-tools, and the two legacy tools directories, in that order, before the existing PATH. The emulator entry explains why this image can run an Android emulator rather than only compile for it.

One environment variable looks unrelated to Android and is not. The file sets LC_ALL=C.UTF8, with a comment explaining that it resolves a known character encoding issue with decompressing tar.gz files in containers via Gradle, and it links the Gradle issue. That is the kind of line that exists only because somebody hit the failure and wrote down the fix.

System dependencies are installed with apt in the same layer, including the usual build toolchain of gcc, g++, make and git, plus curl, file, gnupg2, libc++1-11, libgl1 and libtcmalloc-minimal4, alongside the OpenJDK 17 package.

## Why the Gradle distribution is preloaded into a cache directory

The one part of the README that explains a design decision rather than listing a feature is the Gradle preload.

The image preloads the Gradle Wrapper distribution selected by the GRADLE_VERSION build argument. Its wrapper cache is stored in GRADLE_USER_HOME=/opt/gradle-home, so ./gradlew can start without downloading Gradle in ephemeral CI containers.

That single line describes a real and common failure mode in containerised CI. The Gradle Wrapper normally downloads the distribution it is configured for on first use, and it does that into the Gradle user home. In a fresh container with no volume mounted over that directory, the download happens on every single job, which adds several minutes of network time to a build that might otherwise take one. Preloading moves that cost into the image build, where it is paid once and then inherited by every container started from it.

The catch is that the benefit only survives if the directory is persisted. Because the cache lives at /opt/gradle-home inside the image layer, a container started with a mounted volume over that path keeps whatever the previous job downloaded, and the preloaded copy acts as the seed for the first run. Without a volume, the image still saves the initial download but not the incremental dependency cache that normally accumulates across builds.

The GRADLE_USER_HOME choice also has a second effect. Because the wrapper cache is not in the default location, a build that assumes the default Gradle home will behave differently, and the directory becomes the place to mount for caching both the distribution and the resolved dependencies.

The ADB_INSTALL_TIMEOUT=10 variable set in the same file is the other environment detail worth knowing, since it governs how long the image waits when installing SDK packages over the network.

## Publishing, tags and what the repository actually contains

The repository is small, which is what you would expect from a project whose product is a Dockerfile.

At the top level there is the Dockerfile, a README.md, a LICENSE, a .gitignore, a .github/ directory, a scripts/ directory and publish_docker.js. That last file and the scripts directory exist to get built images into a registry, and the .github/ directory holds the workflows, one of which is the Publish workflow shown in the badges.

The badges tell you the pipeline shape. There is a CircleCI status badge pointing at the main branch, and a GitHub Actions Publish badge, so both continuous integration systems are in play. The Docker Hub badge points at reactnativecommunity/react-native-android, which the README links to as the Dockerhub image.

The release history follows the image versions. The listed releases are v21.0 on 2026-06-19, v21.1 on 2026-09-22 and v22.0 on 2026-10-08, and the last push to the repository was on 2026-10-08. The tags track the image, not the Dockerfile independently, so pinning a tag is how you pin a toolchain.

The licence is MIT, and the repository root carries the LICENSE file. Nothing here suggests a separate licence obligation for the packages the image installs, since those come from their own upstream distributions under their own terms.

One thing the README does not say is worth noting: it does not document how to build the image locally, or which command to run to start a container with the right volume mounts. The documentation is oriented towards pulling the published image from Docker Hub, and a team intending to modify the toolchain would be working from the Dockerfile itself rather than from documented instructions.

## Where this image is the wrong choice

There are several situations where pulling this image costs more than it saves.

The first is when you are not building React Native. The version set is chosen for React Native, and the Node, Watchman and Gradle versions in the ARG block are the ones that project's toolchain expects. An unrelated Gradle project that needs a different JDK, a different Android platform or a different Gradle distribution would be rebuilding the image on every change, which is a worse outcome than installing the SDK directly on the runner.

The second is the size and startup cost. The image carries the Android SDK with platform tools, build tools, the NDK and the emulator, so pulling it is not a lightweight operation, and a CI job that needs one small Gradle task pays for all of it. A purpose-built image per project, or a runner with the SDK preinstalled and cached, can be cheaper for a narrow job.

The third is the platform version drift the Dockerfile warns about itself. Because Android 17 ships as platforms;android-37.0 rather than android-37, a project whose Gradle configuration names the platform the old way will fail against this image. The same applies to NDK and CMake versions: the arguments are overridable, so the fix is to rebuild with your own values, but a mismatch is a build failure rather than a subtle difference.

The fourth is that the preloaded Gradle cache only pays off if you mount it. Running the image with no volume at /opt/gradle-home gives you a faster first Gradle invocation but none of the incremental caching that makes repeated builds cheap, and for a team whose complaint was slow CI, that is the difference between solving the problem and half solving it.

## Conclusion

Adopt docker-android if your React Native Android builds are slow or flaky because every CI job installs its own Android SDK, since the image pins the SDK, NDK, Node, Watchman, CMake, Gradle and JDK versions as build arguments you can override. Do not adopt it as a general purpose Android container, because the versions are chosen for React Native rather than for arbitrary Gradle projects, and the base is Ubuntu with a JDK 17 toolchain rather than a full desktop Android environment. Before you commit, read the ARG block in the Dockerfile and decide which version pins you want to keep, since the image ships Android 17 platform packages in the newer android-37.0 form and a plain android-37 package does not exist, and mount a volume at /opt/gradle-home so the preloaded Gradle cache survives between runs. The licence is MIT, the newest release is v22.0 on 2026-10-08, and the last push was on 2026-10-08.

## FAQ

### What does the react-native-community/docker-android image contain?

It is a Docker image based on ubuntu:22.04 that ships the Android SDK, NDK, Node, Watchman, CMake, Gradle and OpenJDK 17, with versions fixed through build arguments such as SDK_VERSION, ANDROID_BUILD_VERSION, NDK_VERSION, NODE_VERSION, WATCHMAN_VERSION, CMAKE_VERSION and GRADLE_VERSION.

### How do I get the docker-android image?

The README points at the Docker Hub repository reactnativecommunity/react-native-android for the published image, and a working reference setup is in react-native-community/ci-sample. The README does not document a local build command for the Dockerfile, so building a modified image means working from the Dockerfile directly.

### Why does docker-android preload Gradle into GRADLE_USER_HOME?

The image preloads the Gradle Wrapper distribution selected by the GRADLE_VERSION build argument and stores its wrapper cache in GRADLE_USER_HOME=/opt/gradle-home, so ./gradlew can start without downloading Gradle in ephemeral CI containers. The directory is the natural place to mount a volume if you want the cache to survive between runs.

### Which Android platform version does docker-android install?

The Dockerfile sets ANDROID_BUILD_VERSION=37.0 and notes that Android 17, API 37, ships as a versioned platform package platforms;android-37.0 under the new api.minor scheme, and that a plain android-37 package does not exist.

### What is the relationship between docker-android and the react-native repository?

The README says the original version is split from the react-native repository, where it lived as ContainerShip/Dockerfile.android-base, so the image began as the base Android environment used for React Native's own continuous integration and was then generalised.

## Sources

- [Issues](https://github.com/react-native-community/docker-android/issues)
- [License: MIT](https://github.com/react-native-community/docker-android/blob/main/LICENSE)
- [react-native-community/docker-android on GitHub](https://github.com/react-native-community/docker-android)
- [README](https://github.com/react-native-community/docker-android/blob/main/README.md)
- [Releases](https://github.com/react-native-community/docker-android/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/react-native-community-docker-android
