🖥️ Emulator Support
Full Android emulator lifecycle management via Docker containers with KVM hardware acceleration. Create, start, stop, and delete emulators from the dashboard or REST API, and scale to multiple concurrent headless instances for testing.
⭐ New in 1.3: Docker+KVM replaces native AVD
Section titled “⭐ New in 1.3: Docker+KVM replaces native AVD”Through v1.2, Ghost drove emulators through the native Android SDK (avdmanager + emulator). That backend was removed in 1.3 and replaced wholesale with Docker containers (budtmo/docker-android) accelerated by KVM.
Why the switch: the SDK emulator is fiddly to install and did not run reliably on AMD Linux hosts — its host-acceleration path carries Intel/HAXM-era assumptions. A container that only needs /dev/kvm runs the same way on any Linux box: no SDK install, no AVD config, no per-machine GPU tuning. Emulators became reproducible and disposable.
Every old SDK entry point is now a no-op stub, so the rest of Ghost (skills, agent loop, MCP tools) treats an emulator as just another ADB serial and needed no changes.
What this changes for you
Section titled “What this changes for you”createnow means create + start. Posting to/api/emulatorslaunches the container and boots it in one call — there’s no separate “define AVD, then boot” step.- The image is fixed to Android 11 (API 30). Only
budtmo/docker-android:emulator_11.0ships. Fields likeapi_level,gpu,ram_mb,cores, andresolutionare still accepted by the API/create form for compatibility, but are ignored — the container uses its built-in profile (the one knob that applies is the device profile, passed asEMULATOR_DEVICE). So a request with"api_level": 35still boots Android 11. startflags are cosmetic.headless,gpu, andextra_argson the start endpoint are accepted but no-ops — Docker emulators are always headless and use SwiftShader internally.
Known issues after the migration
Section titled “Known issues after the migration”The dashboard still carries a few SDK-era checks that now always fail — cosmetic, not fatal:
- An “Android SDK Not Found” warning can appear on the Emulators tab even when Docker works fine — it keys off legacy
emulator_binary/sdk_rootprerequisite fields that are hardcoded false in Docker mode. - The Create form may render disabled (“cmdline-tools not installed”) for the same reason. Creating via the API (
POST /api/emulators) works regardless. Re-gating the UI on Docker/KVM instead of SDK tooling is tracked as a follow-up.
Architecture
Section titled “Architecture”Docker+KVM backend
Section titled “Docker+KVM backend”Ghost uses budtmo/docker-android containers instead of the Android SDK emulator. Each container runs a full Android 11 emulator, exposes ADB on port 5555, and is hardware-accelerated via KVM.
Ghost backend | vDocker daemon → budtmo/docker-android container | Android 11 (API 30) | ADB server :5555 | vadb connect localhost:<host_port> | vDevice(serial) — same API as a physical phoneWhy Docker instead of SDK AVDs:
- No Android SDK required — just Docker + KVM
- Works on any Linux machine with
/dev/kvm - Emulators are isolated, reproducible, and easy to reset
- No SDK version management headaches
EmulatorPool
Section titled “EmulatorPool”Manages multiple containers for parallel testing:
- Thread-safe serial-to-status tracking (idle/busy)
- System resource monitoring (CPU, RAM, disk)
- Scale up: spin N containers at once
- Scale down: stop idle containers
- Job assignment via mark_busy/mark_idle
Prerequisites
Section titled “Prerequisites”# Checkdocker info # Docker daemon runningls /dev/kvm # KVM available (Linux)adb version # ADB in PATH
# Pull the image (6.9 GB, one-time)docker pull budtmo/docker-android:emulator_11.0
# Verify via Ghostcurl http://localhost:5055/api/emulators/prerequisitesThe prerequisites endpoint returns:
{ "backend": "docker", "docker_available": true, "kvm_available": true, "adb_binary": true, "hw_accel": true, "hw_accel_type": "KVM", "image": "budtmo/docker-android:emulator_11.0"}Quick Start
Section titled “Quick Start”Via dashboard
Section titled “Via dashboard”Open the Emulators tab → click Create → enter a name → the container starts and connects via ADB automatically.
Via API
Section titled “Via API”# Create + startcurl -X POST http://localhost:5055/api/emulators \ -H "Content-Type: application/json" \ -d '{"name": "test-emu", "api_level": 30}'
# Poll boot statuscurl http://localhost:5055/api/emulators/test-emu/boot-status# {"serial": "localhost:5555", "booted": true}
# Use it — same as a physical phonecurl http://localhost:5055/api/phone/devices
# Stop + deletecurl -X POST http://localhost:5055/api/emulators/test-emu/stopcurl -X DELETE http://localhost:5055/api/emulators/test-emuManual Docker (no Ghost backend)
Section titled “Manual Docker (no Ghost backend)”# docker-compose.emulators.yml — ships with the reposervices: emu-1: image: budtmo/docker-android:emulator_11.0 devices: - /dev/kvm:/dev/kvm ports: - "5555:5555"docker compose -f docker-compose.emulators.yml up -dadb connect localhost:5555Emulators = Phones
Section titled “Emulators = Phones”Both emulators and physical phones are ADB serials. Device("localhost:5555") works identically to Device("YOUR_DEVICE_SERIAL"). Every MCP tool, skill, and agent workflow runs unchanged on emulators.
API Reference
Section titled “API Reference”Emulator Management
Section titled “Emulator Management”| Method | Endpoint | Description |
|---|---|---|
| GET | /api/emulators | List all containers with status |
| POST | /api/emulators | Create + start a new container |
| DELETE | /api/emulators/<name> | Stop + remove container |
| POST | /api/emulators/<name>/stop | Stop container |
| GET | /api/emulators/<name>/boot-status | {serial, booted} |
| GET | /api/emulators/running | Running containers reachable via ADB |
| GET | /api/emulators/prerequisites | Docker + KVM availability check |
| GET | /api/emulators/system-images | Available Docker images |
| POST | /api/emulators/system-images/install | Pull a Docker image |
Pool Management
Section titled “Pool Management”| Method | Endpoint | Description |
|---|---|---|
| GET | /api/emulator-pool/status | Active / idle / busy counts |
| GET | /api/emulator-pool/resources | CPU / RAM / disk usage |
| POST | /api/emulator-pool/scale-up | Start N containers |
| POST | /api/emulator-pool/scale-down | Stop idle containers |
| POST | /api/emulator-pool/stop-all | Stop all pool containers |
| File | Purpose |
|---|---|
gitd/services/emulator_service.py | EmulatorManager + EmulatorPool |
gitd/services/_emulator_helpers.py | Docker client, discovery, config dataclass |
gitd/services/_emulator_pool.py | Pool lifecycle |
gitd/routers/emulators.py | REST API routes |
docker-compose.emulators.yml | Manual multi-emulator compose file |
Snapshots
Section titled “Snapshots”Snapshots are not supported in Docker mode — containers start fresh each time. For persistent state, commit a custom Docker image from a running container:
docker commit <container_id> my-emulator-state:v1Related
Section titled “Related”- ADB Device — Device class works identically with emulators
- Scheduler — assign automated jobs to emulators
- Ghost Bench — run benchmark suites across emulator pools