dorker 0.1.4

Docker for headless Android emulator environments
dorker-0.1.4 is not a library.

Install

Requires Rust 1.85+ and ~/.cargo/bin on your PATH.

cargo install dorker

Verify:

dorker --help
git clone https://github.com/cesarferreira/dorker.git
cd dorker
cargo install --path . --locked
# or
make install-release

Debug install (faster compile, larger binary):

make install

Run without installing:

make build-release
./target/release/dorker

Quickstart

Requires the Android SDK (emulator, platform-tools) and an installed system image (dorker pull installs one). macOS Apple Silicon and Linux x86_64 with KVM are validated. No Docker needed.

# Check your setup, then install a system image if one is missing
dorker doctor
dorker pull android:35

# Boot a headless emulator and use it
dorker run -d --name test android:35
dorker ps
dorker exec test "getprop ro.build.version.sdk"
dorker install test ./app-debug.apk
dorker screenshot test shot.png      # waits up to 10s for a non-blank frame
dorker logs test                     # add -f to follow

# Stop it, bring it back (cold boot, keeps its data), then delete it
dorker stop test
dorker start test
dorker rm test

Describe your environment in a Dorkerfile

A Dorkerfile (next to your app, like a Dockerfile) declares the Android environment: the base image, resources, the APK to install, settings, and a snapshot to start from.

# Dorkerfile
FROM android:35

MEMORY 2GB
CPUS 2
DISK 4GB
DEVICE pixel_7
RESOLUTION 1080x2400
HEADLESS true

# Paths are relative to this file's folder.
INSTALL ./app/build/outputs/apk/debug/app-debug.apk

# Turn off animations: faster, less flaky UI tests.
SETTING window_animation_scale 0
SETTING transition_animation_scale 0
SETTING animator_duration_scale 0

# Save a snapshot so instances start in a few seconds,
# and open the app once an instance is ready.
SNAPSHOT ready
CMD launch com.example.app

Then build the image once and run it as many times as you like:

# Install the base image named by FROM, if it is missing
dorker pull

# Build once: installs your APK, applies settings, saves the snapshot
dorker build -t myapp:latest .

# Run as often as you like; each run starts from the snapshot
dorker run -d --name t1 myapp:latest
dorker screenshot t1 shot.png
dorker test t1 \
  --runner com.example.app.test/androidx.test.runner.AndroidJUnitRunner
dorker rm -f t1

# Or run in the foreground; the instance is removed on Ctrl-C
dorker run --rm myapp:latest

An identical rebuild is a cache hit (instant), and every dorker run gets its own isolated copy, so instances never share state. To try it without your own app, use the sample:

# Build the tiny sample APK (needs the Android build-tools and a JDK)
examples/basic/make-apk.sh

dorker build -t basic examples/basic
dorker run -d --name b1 basic
dorker exec b1 "pm list packages | grep dorker"
dorker rm -f b1

Every directive is documented in docs/dorkerfile.md. More examples: examples/basic, examples/instrumentation and examples/counter-app, a real Gradle app with Espresso tests (./dev.sh runs Gradle, dorker build, run, a screenshot and the tests). The example APKs are not committed: build them with each example's make-apk.sh (or Gradle for the counter app).

Each instance gets its own private AVD under ~/.dorker (override with DORKER_HOME). Your existing AVDs are never touched. dorker run --rm removes the instance when it stops. ps, images and doctor accept --json.

Measured on an Apple M4 Pro, API 35, 2GB: cold boot about 15s, snapshot restore about 4s. Host memory is about 2.5GB after a cold boot and 3.3GB after a restore. Details and caveats: docs/benchmarks.md.

Test, benchmark, automate

dorker init                          # write a starter Dorkerfile
dorker test t1 \
  --runner com.example.test/androidx.test.runner.AndroidJUnitRunner
dorker test t1 --runner ... --class com.example.FooTest#bar

# Boot, restore, launch, disk and idle numbers
dorker bench myapp:latest --runs 3 --json

# Also time the plain official emulator, for comparison
dorker bench android:35 --baseline

dorker test prints a summary, preserves the raw instrumentation output and logcat under the instance directory, and exits 7 if anything failed. See examples/instrumentation for a runnable example.

Tests

# Unit, CLI and fake-SDK lifecycle/failure tests (no emulator needed)
cargo test

# End-to-end: boots real emulators (about 3 minutes)
cargo test -- --ignored --test-threads=1

Use it from AI agents

dorker mcp is a Model Context Protocol server: an agent can start instances, install apps, run commands, take screenshots, read logs and run tests, all with structured results.

claude mcp add dorker -- dorker mcp

See docs/mcp.md for the tools, other clients and security notes. The same operations are available to scripts as --json on run, start, exec, install, screenshot, test, ps, images, doctor and bench.

Diagnostics

dorker doctor colours its report on a terminal (green ok, yellow warn, bold red FAIL, fixes in cyan). Colour is off when piped, NO_COLOR turns it off, CLICOLOR_FORCE=1 forces it on, and --json is never coloured.

dorker -v <command> (or DORKER_VERBOSE=1) prints what dorker decides and runs to stderr: the located SDK, the boot mode and why, the exact emulator command line and environment, state transitions and timings. Each instance also keeps its raw emulator output in ~/.dorker/instances/<id>/emulator.log.

Exit codes

Code Meaning
0 success
1 other error
2 invalid configuration or arguments (including Dorkerfile errors)
3 missing dependency (SDK, emulator, adb, system image); doctor failures
4 image build failed
5 instance failed to start or boot
6 adb operation failed
7 instrumentation tests failed
8 name, port or build conflict, or not enough free disk space

dorker exec returns the guest command's own exit status.

See docs/architecture.md for design and limitations.

Limitations

  • Platform: validated on macOS Apple Silicon (primary) and Linux x86_64 with KVM (Ubuntu 24.04, an Azure VM with nested virtualisation; unit tests and the full emulator end-to-end suite pass). Linux arm64 and bare-metal Linux are untested; Windows is unsupported. Linux is slower in our measurements (cold boot about 29s, snapshot restore about 9s; see docs/benchmarks.md).
  • System images are installed on request, not automatically. dorker pull android:35 runs Google's android sdk install (the replacement for the deprecated sdkmanager; older command-line tools fall back to sdkmanager). The new CLI accepts the Android SDK licences (and the ARM translation licence for arm64 images) for you without prompting, so pull says so first, and Google documents that it collects usage data (opt out with android --no-metrics); dorker run/build print this command when an image is missing. The download path itself was only verified for the already-installed and nonexistent-package cases, not a real multi-GB download.
  • Snapshots are strict. A change of emulator version, system-image revision, memory or CPUs invalidates an image's snapshot. dorker then cold boots (about 15s) and says so. Snapshots are host-specific and not portable between machines.
  • Memory: a 2GB guest used about 2.5GB of host memory after a cold boot and 3.3GB after a snapshot restore on macOS (3-3.7GB on Linux). Idle CPU is dominated by Android background work and takes about a minute to settle.
  • Builds take about 45s because dorker reboots the guest to persist provisioned state.
  • CMD only supports launch <package>. dorker start always cold boots (about 14s) because restoring the image snapshot would roll back the instance's data. There is software rendering (swiftshader_indirect) is the default; GPU <mode> in the Dorkerfile selects another, but none was faster for snapshot restore in our measurements (see docs/benchmarks.md) and no automatic test-APK discovery.
  • Concurrency: creating instances concurrently is safe, and stop, rm and start on the same instance are serialised by a per-instance lock (the second command waits up to 60s, then exits 8).
  • Orphans: emulator processes that dorker started but lost track of (CLI killed mid-start) are reported by dorker ps, not adopted or killed automatically.
  • examples/basic and examples/instrumentation use tiny hand-built APKs; examples/counter-app is the realistic one.

Development

Common tasks via the Makefile:

make              # check + build + test
make build        # debug build
make build-release
make install      # install debug binary
make install-release
make run ARGS="doctor"
make check        # cargo check + clippy
make fmt          # format
make lint         # fmt check + clippy
make test
make clean
make demo         # install + show --help

Releasing (requires cargo-release and git-cliff):

make release                  # default minor bump
make release LEVEL=patch      # patch bump
make release LEVEL=major      # major bump

The pre-release hook regenerates CHANGELOG.md with git-cliff from your conventional-commit history (grouped into Features, Bug Fixes, etc. per cliff.toml) and commits it alongside the version bump. Pushing the resulting v* tag triggers the release workflow, which builds the multi-platform binaries and publishes a GitHub Release whose notes are generated by git-cliff from the same config.

License

MIT