The Problem

Running Android emulators in CI/CD or remote test environments is painful: they need KVM acceleration, large disk space, and a clean, reproducible environment. Hand-provisioning emulators per build machine is slow and non-deterministic. This project packages the Android emulator as a Docker service so teams can spin up a disposable, headless Android device on demand.

What This Does

docker-android builds a minimal Alpine-based Docker image containing the Android emulator, an ADB server, and QEMU with KVM support. The Dockerfile is parameterized via build args to select the Android API level, device profile, and image type (e.g., google_apis_playstore). It also provides a docker-compose.yml with profiles for CPU-only, GPU-accelerated, and Play Store variants.

The scripts/ directory contains the operational logic: install-sdk.sh downloads the SDK, start-emulator.sh launches the emulator headless, and emulator-monitoring.sh provides a health-check loop. The keys/ directory holds ADB keys so clients can authenticate to the emulator.

How It Is Wired

Execution starts at the container's ENTRYPOINT defined in the Dockerfile, which runs scripts/start-emulator.sh. That script:

  1. Sources environment variables (API level, device, image type).
  2. Invokes scripts/install-sdk.sh if the SDK is not already present.
  3. Starts the ADB server and launches the emulator with -no-window -no-audio.
  4. Exposes ports 5555 (ADB) and 5554 (emulator console) on the container network.

The docker-compose.yml wires the container to the host's /dev/kvm device and publishes port 5555. The ADB connection leaves the container via the network to the client's adb connect 127.0.0.1:5555.

The image is large (5.84 GB uncompressed for API 33), so the build process is I/O heavy. The Dockerfile.gpu adds CUDA support via a separate profile.

File map:

  • Dockerfile / Dockerfile.gpu — build definition and entrypoint.
  • scripts/install-sdk.sh — SDK and emulator package download.
  • scripts/start-emulator.sh — emulator boot sequence and port forwarding.
  • scripts/emulator-monitoring.sh — container health checks.
  • keys/adbkey / keys/adbkey.pub — ADB authentication pair.
  • docker-compose.yml — orchestration for CPU/GPU/Play Store variants.

How To Use It

Setup: Build the image with Docker:

docker build -t android-emulator .

Configuration: For Play Store images, generate your own ADB key pair and place them in keys/:

adb keygen adbkey

Running it: Use Docker Compose for the simplest path:

docker compose up android-emulator

Or run directly, mounting KVM:

docker run -it --rm --device /dev/kvm -p 5555:5555 android-emulator

Then connect from a client:

adb connect 127.0.0.1:5555

Real-World Use

A mobile QA team runs this container in a Jenkins pipeline. Each test job starts a fresh emulator, runs an Espresso test suite via adb, and tears down the container. Because the image wipes emulator data on restart, every run starts from a clean state, eliminating flaky tests caused by stale app state.

docker compose up android-emulator
# Run tests
./gradlew connectedAndroidTest
adb shell am instrument -w com.example.test/androidx.test.runner.AndroidJUnitRunner
docker compose down

Code Health & Issues

Static analysis found 6 issues (0 critical, 2 high, 3 medium, 1 low):

  • High — docker/login-action@v2 and docker/build-push-action@v4 in .github/workflows are tag-pinned; a moved tag can execute arbitrary code with your CI secrets. Pin to commit SHAs.
  • High — docker-compose.yml sets privileged: true, granting all capabilities and direct device access. Use cap_add for KVM only.
  • Medium — workflows in .github/workflows/ declare no permissions block, so GITHUB_TOKEN inherits repo defaults. Add contents: read at the top level.
  • Medium — Dockerfile uses openjdk:21-jdk-slim without a digest, making builds non-reproducible. Pin by sha256: digest.
  • Medium — No USER directive in the Dockerfile; the emulator runs as root. Create an unprivileged user.
  • Low — Workflow jobs have no timeout-minutes, risking overlapping runs on long failures.

No test files were detected. CI exists (Travis CI and GitHub Actions), and a license is present. No committed secrets were found.

The Bottom Line

This is a practical, well-structured solution for teams that need ephemeral Android emulators in CI. The core design is sound; the main risks are the security defaults (privileged mode, root user) and the lack of pinned dependencies, both of which should be fixed before production use. Suitable for QA automation and remote device farms, not for untrusted workloads.