Skip to content

Repository files navigation

Mobile Device Operator

CI License

Declare an Android device in Kubernetes, wait until it is ready, run commands on it, capture its screen, and open an interactive viewer.

Status: experimental alpha. MDO is a working prototype for exploring a provider-neutral Kubernetes device API. It is not a production device cloud and currently targets Android only.

Mobile Device Operator (MDO) is a deliberately small, backend-agnostic Kubernetes operator prototype. It explores what mobile-device infrastructure could look like when a phone or emulator is represented by a declarative API instead of an external testing dashboard.

Recorded demo

A CI-generated Mobile Device Operator Android workflow demo

This animation is rendered from checks already validated against a real hosted Android Device; VHS only presents the verified transcript and never owns Kubernetes, ADB, or MDO processes.

The CI artifact also contains the terminal MP4, Android viewer evidence (view-android.mp4), the real Android screenshot, the E2E summary, the VHS tape, and checksums. See docs/demo for the reproducible generation and publication flow.

MDO prototype flow: Device manifest, Kubernetes control plane, Android emulator and CLI access

The prototype in four commands

Assuming the operator, CRDs, DeviceClass, and ADB key are installed:

kubectl apply -f config/samples/devices_v1alpha1_device.yaml
kubectl wait --for=condition=Ready device/mobile-demo --timeout=20m
bin/mdo exec mobile-demo -- getprop sys.boot_completed
bin/mdo screenshot mobile-demo --output phone.png

The same Device can be attached to local ADB or opened with a local interactive viewer:

bin/mdo connect mobile-demo
bin/mdo view mobile-demo

These are not mocked API responses. The hosted E2E starts Kubernetes and an Android emulator, exercises the real CLI paths, captures a valid PNG, validates the mdo view viewer-launch contract through the MDO-managed ADB tunnel, and stores the complete evidence as workflow artifacts.

What MDO demonstrates

Device manifest
    │
    ▼
DeviceClass selects an Android provider image
    │
    ▼
Controller creates the provider workload
    │
    ▼
Provider workload starts
Device.status.phase = Starting
Ready=False / ProviderStarting
    │
    ▼
Provider-specific readiness succeeds
    │
    ▼
Device.status.phase = Ready and publishes the endpoint
    │
    ├── mdo connect     persistent local ADB session
    ├── mdo exec        one-shot Android shell command
    ├── mdo screenshot  one-shot PNG capture
    └── mdo view        local interactive viewer (scrcpy first)

Starting is a provider-neutral lifecycle phase: it means that the backing runtime is running, but the selected provider has not yet declared the device usable. Ready is therefore a consumer contract, not merely a container-health signal. Each provider owns the platform-specific checks behind that boundary; the core Device lifecycle only consumes the provider's Kubernetes readiness result.

For the current Android emulator provider, the provider workload does not become ready until Android has completed boot, the current user is unlocked, a real HOME activity has replaced the startup fallback, and that HOME activity is actually resumed. Those Android details do not appear in the public Device API.

The important experiment is the control-plane contract: callers use the same Kubernetes Device API while provider-specific details remain behind DeviceClass and the provider implementation.

Example API

apiVersion: devices.mdo.io/v1alpha1
kind: DeviceClass
metadata:
  name: android-emulator-default
spec:
  platform: android
  provider: android-emulator
  image: us-docker.pkg.dev/android-emulator-268719/images/30-google-x64:30.1.2
---
apiVersion: devices.mdo.io/v1alpha1
kind: Device
metadata:
  name: mobile-demo
spec:
  platform: android
  className: android-emulator-default

MDO exposes its class resource as mobiledeviceclasses.devices.mdo.io, with the unambiguous short name mdo-class, because Kubernetes also has a native DeviceClass kind.

Quick start: Linux + KVM + Minikube

The current Android provider requires a Linux x86-64 environment with usable /dev/kvm. The development setup is intentionally heavyweight: plan for at least 6 CPU cores, 12 GiB RAM, and roughly 40 GiB free on the container-runtime filesystem. See docs/MINIKUBE_ANDROID_E2E.md before using Minikube's none driver.

Start Minikube and label the KVM-capable node:

sudo -E env CHANGE_MINIKUBE_NONE_USER=true minikube start \
  --driver=none \
  --container-runtime=containerd

NODE_NAME="$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')"
kubectl label node "$NODE_NAME" devices.mdo.io/kvm=true --overwrite

Install the APIs and default Android DeviceClass:

kubectl apply -f config/crd/bases/devices.mdo.io_mobiledeviceclasses.yaml
kubectl apply -f config/crd/bases/devices.mdo.io_devices.yaml
kubectl apply -f config/samples/devices_v1alpha1_deviceclass.yaml

Build the operator image inside Minikube and deploy the controller:

minikube image build -t mobile-device-operator:e2e .
kubectl apply -f config/deploy/minikube.yaml
kubectl rollout status \
  deployment/mobile-device-operator-controller-manager \
  --namespace mobile-device-operator-system \
  --timeout=180s

Authorize the local ADB identity. Only the public key is copied into Kubernetes; keep $HOME/.android/adbkey private.

adb start-server
kubectl create secret generic mdo-adb-public-key \
  --from-file=adbkey.pub="$HOME/.android/adbkey.pub" \
  --dry-run=client -o yaml | kubectl apply -f -

Create a Device, wait on the portable readiness contract, and build the CLI:

kubectl apply -f config/samples/devices_v1alpha1_device.yaml
kubectl wait --for=condition=Ready device/mobile-demo --timeout=20m
make build-mdo

Use persistent access, one-shot commands, screenshot capture, or the local viewer:

bin/mdo connect mobile-demo
bin/mdo exec mobile-demo -- getprop ro.product.model
bin/mdo screenshot mobile-demo --output phone.png
bin/mdo view mobile-demo

The CLI depends on locally installed kubectl and adb and uses the current kubeconfig context. mdo view additionally requires scrcpy; see docs/VIEW.md for the design decision, viewer options, hosted contract validation, and manual interactive test. Existing screenshot files are not overwritten; raw PNG bytes can be written with --output -.

Pull-request proof

For eligible same-repository pull requests, one privileged Android E2E job validates the DeviceClass and Device lifecycle, mdo connect, mdo exec, a real PNG produced by mdo screenshot, and the mdo view launch contract. The E2E waits on the public Device Ready condition before using the device; provider-specific boot synchronization belongs to the provider readiness implementation, not to CI callers.

The hosted viewer probe is deliberately named scrcpy so the public command follows its normal lookup path, then verifies that MDO supplies the expected TCP ADB serial, forward mode, IPv4 tunnel host, process lifecycle, and cleanup. It also captures a real Android frame through that serial and produces deterministic-duration video evidence.

The hosted check does not claim to certify the real scrcpy client/server or desktop interaction. Actual scrcpy rendering, mouse, keyboard, clipboard, rotation, and graphical-window behavior remain a workstation validation. This keeps CI deterministic while still proving the MDO-specific integration boundary.

The recorded demo reuses the evidence artifact from that same Android E2E job. It does not start a second emulator: a lightweight renderer job turns the verified results into the terminal animation and final media.

Contributing and security

Contributions are welcome; see CONTRIBUTING.md. Please report suspected vulnerabilities privately according to SECURITY.md.

Deliberate limits

MDO is an experimentation platform, not a production device cloud. The prototype intentionally excludes public gateways, accounts, billing, multi-tenancy, browser streaming, recording services, production availability guarantees, iOS support, and multiple production-ready providers.

The long-term experiment is a provider-neutral API that could represent different Android runtimes or physical-device pools without changing the consumer workflow. See docs/PROJECT.md for the vision, architecture, scope, and roadmap.

About

Experimental Kubernetes-native control plane for mobile devices

Topics

Resources

Contributing

Security policy

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages