# Docker-Android
Docker-Android is a Docker image designed to provide a complete Android development and testing environment inside a container. It supports running Android emulators (Android 9.0–14.0) with hardware acceleration via KVM, as well as integration with Genymotion cloud virtual devices (SaaS and AWS). The project enables native, web, and hybrid Android app testing without requiring a physical device, making it well-suited for CI/CD pipelines, cloud deployments, and automated UI testing frameworks such as Appium and Espresso.
The core functionality is orchestrated by a Python-based CLI (`docker-android`) that launches and manages the emulator lifecycle (create, start, wait-for-boot, reconfigure, keep-alive), VNC servers for visual access, a web VNC proxy (noVNC), a log-sharing HTTP server, and an Appium test server — all configurable entirely through environment variables passed to the Docker container. Device profiles and skins are bundled for a range of Samsung Galaxy and Nexus devices, and the emulator config can be overridden by mounting a custom `.ini` file. The build and release pipeline (`app.sh` + GitHub Actions) automates testing, building, tagging, and pushing images for each supported Android version.
---
## Running the Android Emulator Container
Start a containerized Android 11.0 emulator with a Samsung Galaxy S10 device profile and web VNC access. KVM (`/dev/kvm`) must be available on the host for hardware acceleration.
```bash
docker run -d \
-p 6080:6080 \
-e EMULATOR_DEVICE="Samsung Galaxy S10" \
-e WEB_VNC=true \
--device /dev/kvm \
--name android-container \
budtmo/docker-android:emulator_11.0
# Open the emulator in a browser
# http://localhost:6080
# Check emulator boot status
docker exec -it android-container cat device_status
# Output: ready
```
---
## Environment Variables — Emulator Configuration
All emulator behaviour is controlled via environment variables. The table below covers the key knobs; pass them with `-e KEY=value` to `docker run`.
```bash
# Full-featured run: custom name, large data partition, Appium, log sharing, VNC with password
docker run -d \
-p 6080:6080 \
-p 5900:5900 \
-p 4723:4723 \
-p 9000:9000 \
-e EMULATOR_DEVICE="Samsung Galaxy S9" \
-e EMULATOR_NAME="my_s9_emu" \
-e EMULATOR_DATA_PARTITION="900m" \
-e EMULATOR_NO_SKIN=false \
-e EMULATOR_ADDITIONAL_ARGS="-memory 2048" \
-e WEB_VNC=true \
-e WEB_VNC_PORT=6080 \
-e VNC_PASSWORD=secret123 \
-e WEB_LOG=true \
-e WEB_LOG_PORT=9000 \
-e APPIUM=true \
-e APPIUM_ADDITIONAL_ARGS="--allow-insecure chromedriver_autodownload" \
--device /dev/kvm \
--name android-full \
budtmo/docker-android:emulator_13.0
# Available EMULATOR_DEVICE values:
# Nexus 4, Nexus 5, Nexus 7, Nexus One, Nexus S
# Samsung Galaxy S6, S7, S7 Edge, S8, S9, S10
# Pixel C, Pixel 8, Pixel 9
# Supported Android versions (image tags):
# emulator_9.0 (API 28)
# emulator_10.0 (API 29)
# emulator_11.0 (API 30)
# emulator_12.0 (API 32)
# emulator_13.0 (API 33)
# emulator_14.0 (API 34)
```
---
## Persisting Emulator Data
By default the emulated device is wiped on container restart. Mount a named volume at `/home/androidusr` to persist app data, AVD state, and emulator storage across restarts.
```bash
# Create a named volume and mount it
docker run -d \
-p 6080:6080 \
-e EMULATOR_DEVICE="Nexus 5" \
-e WEB_VNC=true \
--device /dev/kvm \
-v android-data:/home/androidusr \
--name android-persistent \
budtmo/docker-android:emulator_14.0
# On subsequent restarts the AVD is reused (no --wipe-data)
docker restart android-persistent
```
---
## Overriding the Emulator Config File
Append custom AVD `config.ini` options at container startup by mounting a file and pointing `EMULATOR_CONFIG_PATH` to it.
```bash
# emulator-override-config.ini (on host)
# hw.ramSize=3072
# hw.cpu.ncore=4
docker run -d \
-p 6080:6080 \
-e EMULATOR_DEVICE="Samsung Galaxy S10" \
-e WEB_VNC=true \
-e EMULATOR_CONFIG_PATH=/tmp/emulator-override-config.ini \
-v /path/on/host/emulator-override-config.ini:/tmp/emulator-override-config.ini \
--device /dev/kvm \
--name android-custom-cfg \
budtmo/docker-android:emulator_14.0
```
---
## VNC Access — Native Client and Web UI
The VNC server runs on port 5900; the noVNC web proxy runs on port 6080. Both can be secured with a password.
```bash
# Expose both ports
docker run -d \
-p 5900:5900 \
-p 6080:6080 \
-e EMULATOR_DEVICE="Nexus 5" \
-e WEB_VNC=true \
-e VNC_PASSWORD=mypassword \
--device /dev/kvm \
--name android-vnc \
budtmo/docker-android:emulator_11.0
# Connect with a native VNC client (e.g. TigerVNC, RealVNC)
# Host: localhost Port: 5900 Password: mypassword
# Web UI endpoints (noVNC)
# Auto-connect: http://localhost:6080/?autoconnect=true
# View-only: http://localhost:6080/?autoconnect=true&view_only=true
# Auto-connect with password: http://localhost:6080/?autoconnect=true&password=mypassword
```
---
## Log Sharing via Web UI
Enable a built-in HTTP log server to browse container log files from a browser.
```bash
docker run -d \
-p 6080:6080 \
-p 9000:9000 \
-e EMULATOR_DEVICE="Nexus 5" \
-e WEB_VNC=true \
-e WEB_LOG=true \
-e WEB_LOG_PORT=9000 \
--device /dev/kvm \
--name android-logs \
budtmo/docker-android:emulator_11.0
# Browse log index
curl http://localhost:9000/
#
appium.log
...
# Fetch a specific log file
curl http://localhost:9000/appium.log
```
---
## Running Appium Server
Enable the Appium 2.x server inside the container for mobile UI testing. The server starts on port 4723.
```bash
docker run -d \
-p 6080:6080 \
-p 4723:4723 \
-e EMULATOR_DEVICE="Samsung Galaxy S10" \
-e WEB_VNC=true \
-e APPIUM=true \
-e APPIUM_ADDITIONAL_ARGS="--relaxed-security" \
--device /dev/kvm \
--name android-appium \
budtmo/docker-android:emulator_11.0
# Verify Appium is up
curl http://localhost:4723/status
# {"value":{"build":{"version":"2.x.x"},"ready":true},...}
# Run a Python Appium test against the container
# pip install Appium-Python-Client
from appium import webdriver
desired_caps = {
"platformName": "Android",
"deviceName": "emulator-5554",
"app": "/path/to/app.apk",
"automationName": "UiAutomator2"
}
driver = webdriver.Remote("http://localhost:4723", desired_caps)
driver.find_element("id", "com.example.app:id/button").click()
driver.quit()
```
---
## Controlling the Emulator via ADB from the Host
Expose ADB ports so the host `adb` can connect directly into the container emulator.
```bash
docker run -d \
-p 5554:5554 \
-p 5555:5555 \
-e EMULATOR_DEVICE="Nexus 5" \
--device /dev/kvm \
--name android-adb \
budtmo/docker-android:emulator_11.0
# Connect from host
adb connect :5555
# connected to 192.168.1.100:5555
adb devices
# List of devices attached
# 192.168.1.100:5555 device
# Install an APK
adb -s 192.168.1.100:5555 install myapp.apk
# SMS simulation
docker exec -it android-adb adb emu sms send +1234567890 "Hello from Docker-Android"
```
---
## Building an Android Project Inside the Container
Use the Docker-Android image as a build environment by mounting the project and overriding the entrypoint.
```bash
# Clone a sample project
git clone git@github.com:android/testing-samples.git
# Build the Espresso sample with Gradle inside the container
docker run -it --rm \
-v $PWD/testing-samples/ui/espresso/BasicSample:/home/androidusr/tmp \
-w /home/androidusr/tmp \
--entrypoint "/bin/bash" \
budtmo/docker-android:emulator_11.0_v2.0 \
-c "./gradlew build"
# Run only unit tests
docker run -it --rm \
-v $PWD/testing-samples/ui/espresso/BasicSample:/home/androidusr/tmp \
-w /home/androidusr/tmp \
--entrypoint "/bin/bash" \
budtmo/docker-android:emulator_11.0_v2.0 \
-c "./gradlew test"
```
---
## Genymotion SaaS Integration
Run Genymotion cloud virtual devices instead of a local emulator by providing an auth token and a `saas.json` device template.
```bash
# saas.json — define one or more cloud devices
cat > saas.json <<'EOF'
[
{
"name": "SamsungS10-Test",
"template": "a2a0c86c-7572-45fe-98d0-66f8efed9fa0",
"local_port": 51345
},
{
"template": "80a67ae9-430c-4824-a386-befbb19518b9"
}
]
EOF
export AUTH_TOKEN="your-genymotion-auth-token"
docker run -d \
-p 4723:4723 \
-v $PWD/saas.json:/home/androidusr/genymotion_template/saas.json \
-e DEVICE_TYPE=geny_saas \
-e GENY_AUTH_TOKEN=${AUTH_TOKEN} \
-e APPIUM=true \
--name android-geny-saas \
budtmo/docker-android:genymotion
# Devices are ADB-connected automatically inside the container.
# Stop the container to remove cloud devices and logout:
docker stop android-geny-saas
```
---
## Genymotion AWS Integration
Provision Genymotion AMI instances on AWS via Terraform automatically managed by the container.
```bash
# aws.json — define EC2-backed Genymotion devices
cat > aws.json <<'EOF'
[
{
"name": "device1",
"region": "eu-west-1",
"ami": "ami-68d78411",
"instance_type": "t2.small",
"ingress_rules": [
{"from_port": 22, "to_port": 22, "protocol": "tcp", "cidr_blocks": ["0.0.0.0/0"]},
{"from_port": 51000, "to_port": 51100, "protocol": "tcp", "cidr_blocks": ["0.0.0.0/0"]}
],
"egress_rules": [
{"from_port": 0, "to_port": 65535, "protocol": "udp", "cidr_blocks": ["0.0.0.0/0"]}
]
}
]
EOF
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
docker run -it --rm \
-p 4723:4723 \
-v $PWD/aws.json:/home/androidusr/genymotion_template/aws.json \
-e DEVICE_TYPE=geny_aws \
-e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
-e APPIUM=true \
budtmo/docker-android:genymotion
# Terraform init/plan/apply runs automatically.
# SSH tunnel to ADB is set up and adb connect is called.
# On container exit, terraform destroy is run automatically.
# Allow ~3 min for AWS resource teardown:
docker stop --time=180 android-geny-aws
```
---
## CLI — `docker-android start `
The internal Python CLI manages each sub-service independently. These commands are invoked by the container entrypoint, but can be called manually for debugging.
```bash
# Start the Android emulator device
docker exec android-container docker-android start device
# Start only Appium server (APPIUM=true must be set)
docker exec android-container docker-android start appium
# Start VNC server
docker exec android-container docker-android start vnc_server
# Start noVNC web proxy (WEB_VNC=true must be set)
docker exec android-container docker-android start vnc_web
# Start virtual display (Xvfb)
docker exec android-container docker-android start display_screen
# Start window manager (Openbox)
docker exec android-container docker-android start display_wm
# Start ADB port forwarder (socat, exposes ports 5554/5555)
docker exec android-container docker-android start port_forwarder
```
---
## CLI — `docker-android share log`
Start the built-in HTTP log-sharing server (equivalent to setting `WEB_LOG=true`).
```bash
# Manually trigger log sharing on port 9000
docker exec -e WEB_LOG=true -e WEB_LOG_PORT=9000 \
-e LOG_PATH=/home/androidusr/log \
android-container docker-android share log
# GET / → HTML index of available log files
curl http://localhost:9000/
# GET / → raw log content
curl http://localhost:9000/emulator.log
```
---
## `app.sh` — Build, Test, and Push Images
The `app.sh` script drives local development tasks: running unit tests in a Docker container, building images, and pushing to Docker Hub.
```bash
# Run unit tests (spins up python:3.12-slim container)
./app.sh test emulator test 11.0
# Build emulator image for Android 14.0, release v2.1.0-p0
./app.sh build emulator v2.1.0-p0 14.0
# Produces:
# budtmo/docker-android:emulator_14.0_v2.1.0-p0 (specific release)
# budtmo/docker-android:emulator_14.0 (latest for this Android version)
# budtmo/docker-android:latest (if 14.0 is the last supported version)
# Build the base image
./app.sh build base v2.1.0-p0
# Build the Genymotion image
./app.sh build genymotion v2.1.0-p0
# Push an emulator image (runs build first, then docker push)
./app.sh push emulator v2.1.0-p0 13.0
```
---
## WSL2 Setup (Windows 11 — Hardware Acceleration)
Enable nested virtualization for KVM access in WSL2 before running the container on Windows 11.
```powershell
# 1. Add user to kvm group (inside WSL2 terminal)
sudo usermod -a -G kvm ${USER}
# 2. Configure /etc/wsl.conf (inside WSL2)
# [boot]
# command = /bin/bash -c 'chown -v root:kvm /dev/kvm && chmod 660 /dev/kvm'
# 3. Configure .wslconfig (PowerShell on Windows host)
notepad $env:USERPROFILE\.wslconfig
# Add:
# [wsl2]
# nestedVirtualization=true
# 4. Restart WSL2 (PowerShell)
wsl --shutdown
```
```bash
# After WSL2 restart, run the container normally
docker run -d \
-p 6080:6080 \
-e EMULATOR_DEVICE="Nexus 5" \
-e WEB_VNC=true \
--device /dev/kvm \
budtmo/docker-android:emulator_11.0
```
---
## Cloud Deployment Requirements
Nested virtualization must be enabled on the underlying VM when running Docker-Android in a cloud environment.
```bash
# Azure — use Dv3 or Ev3 VM series (supports nested virtualization)
# https://docs.microsoft.com/en-us/azure/virtual-machines/windows/nested-virtualization
# AWS — use EC2 Bare Metal instances (e.g. i3.metal)
# https://aws.amazon.com/blogs/aws/new-amazon-ec2-bare-metal-instances-with-direct-access-to-hardware/
# GCP — enable nested virtualization on the instance
gcloud compute instances create android-ci \
--machine-type=n2-standard-4 \
--min-cpu-platform="Intel Cascade Lake" \
--enable-nested-virtualization \
--zone=us-central1-a
# Then SSH in and run the container as usual
docker run -d \
-p 6080:6080 \
-p 4723:4723 \
-e EMULATOR_DEVICE="Samsung Galaxy S10" \
-e WEB_VNC=true \
-e APPIUM=true \
--device /dev/kvm \
budtmo/docker-android:emulator_14.0
```
---
## Pro Version — Proxy, Language, and Headless Mode
Docker-Android-Pro (sponsor-only, `budtmo2/docker-android-pro`) adds proxy support, language configuration, headless mode, and newer Android versions (15.0, 16.0).
```bash
# Pro: emulator with corporate proxy and Spanish locale
docker run -d \
-p 6080:6080 \
-e EMULATOR_DEVICE="Samsung Galaxy S10" \
-e WEB_VNC=true \
-e HTTP_PROXY="http://172.17.0.1:3128" \
-e HTTPS_PROXY="http://172.17.0.1:3128" \
-e NO_PROXY="localhost" \
-e EMULATOR_PROXY_URL="http://172.17.0.1:3128" \
-e EMULATOR_LANGUAGE="es" \
-e EMULATOR_COUNTRY="ES" \
--device /dev/kvm \
budtmo2/docker-android-pro:emulator_14.0
# Pro headless mode (no Web-UI, saves resources)
docker run -d \
-p 4723:4723 \
-e EMULATOR_DEVICE="Nexus 5" \
-e APPIUM=true \
--device /dev/kvm \
budtmo2/docker-android-pro:emulator_headless_14.0
# Pro Selenium node (connects to a Selenium Grid 4.x hub)
docker run -t --rm \
--name selenium-node \
-p 4444:4444 \
-v $PWD/pro-example/node.json:/home/seleniumusr/selenium_node_config/node.json \
budtmo2/docker-android-pro:selenium
```
---
Docker-Android's primary use cases span automated mobile CI/CD pipelines, Appium-based UI testing, Android build servers, and cloud-based emulator farms. In a typical CI scenario the image is pulled in a pipeline job, started with `APPIUM=true` and the desired `EMULATOR_DEVICE`, tests are executed against `http://localhost:4723`, and the container is discarded — all without a physical device or a persistent machine. The built-in log sharing and noVNC web UI make it straightforward to debug failures without SSH access.
Integration patterns follow a few common shapes: (1) **Standalone emulator** — a single `docker run` command with environment variables, suitable for local development or simple CI runners; (2) **Appium + Selenium Grid** — multiple Docker-Android containers each running Appium 2.x, connected to a Selenium Grid 4.x hub for parallel cross-device testing, using the Pro Selenium node image; (3) **Genymotion cloud** — the `genymotion` image variant manages cloud virtual device lifecycle via `gmsaas` (SaaS) or Terraform (AWS), abstracting device provisioning entirely; (4) **Build-only** — the entrypoint is overridden to run Gradle directly, using the Android SDK and build tools bundled in the image without starting the emulator.