Skip to content

Emulator

wody edited this page May 24, 2026 · 5 revisions

Android Emulator

Three-stage rollout:

Version What ships
v0.19.0 Scaffolding — /emulator diagnostics (KVM / SDK / AVD / running devices) + ADB install + manual launch guide. Read-only.
v0.24.0 AVD lifecycle on the slim image — + create default, ▶ headless start, ■ stop POST forms. :full emulator-entrypoint.sh written but image not yet published.
v0.25.0 :full image published (siamakerlab/vibe-coder-server:full, ~3-4 GB) with qemu + Xvfb + x11vnc + websockify + noVNC pre-installed. docker/compose.full.yml example.

Quick path to a working browser-based emulator (v0.25.0+) see the "compose.full.yml override" section below.

View at /emulator (left nav: "Emulator").

Why scaffolding-only in v0.19.0

The slim base image (~600 MB) ships JDK + Node + git + the server body — that's it. A full Android emulator needs:

  • KVM (/dev/kvm accessible inside the container) — host-specific
  • qemu-system-x86_64 + libs — ~400 MB
  • X server (Xvfb) + websockify + noVNC — ~150 MB
  • Android system-image — 1–2 GB depending on API level

Adding all of those to the default image triples its size for users who will never touch the emulator. The decision: keep the default slim, provide a :full variant for users who really want in-browser emulator mirroring, and let :default users still run an emulator on the host and use the diagnostics page to inspect it.

What /emulator shows in v0.19.0

EmulatorService.diagnose() runs at every page load and reports:

Item Source
KVM available /dev/kvm exists and is readable+writable
emulator binary $ANDROID_HOME/emulator/emulator
adb binary $ANDROID_HOME/platform-tools/adb
Installed AVDs avdmanager list avd -c
Running devices adb devices (drops the header)
Recommendation dynamic text — what to do next based on the above

Enabling KVM passthrough

By default the compose service runs unprivileged with no device access. KVM requires you to grant access:

services:
  vibe-coder-server:
    devices:
      - /dev/kvm:/dev/kvm
    # If running on a host where /dev/kvm is owned by the kvm group:
    group_add:
      - "${KVM_GID:-104}"   # adjust to match `getent group kvm | cut -d: -f3`

And ensure the host operator is in the kvm group:

sudo usermod -aG kvm $USER
# logout / login to take effect

Without KVM the emulator falls back to software emulation — usually 10× slower. Useful for one-off "did the layout render?" checks but not for realistic perf testing.

Manual launch flow (v0.19.0)

The /emulator page renders the full guide inline. Excerpts:

1. Install a system image

docker exec -it --user vibe vibe-coder-server bash
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager 'system-images;android-35;google_apis;x86_64'
$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd \
    -n test \
    -k 'system-images;android-35;google_apis;x86_64'
exit

2. Start the emulator (headless)

docker exec -d --user vibe vibe-coder-server \
    bash -c '$ANDROID_HOME/emulator/emulator -avd test -no-window -no-audio &'

-no-window runs without a GUI — fine because v0.19.0 doesn't include the noVNC display anyway. ADB still works.

3. Verify and use

Refresh /emulator — the new device should appear under "Running devices".

To install a freshly built APK, ask Claude in the project console:

"Install the latest debug APK on the running emulator and launch the main activity."

Claude knows how to find the APK under <workspace>/.vibecoder/<projectId>/artifacts/debug/<buildId>/*.apk and adb install -r it. The internal EmulatorService.installApk(deviceSerial, apkPath) is also available for direct programmatic use.

AVD lifecycle (v0.24.0+) — SSR forms

The /emulator page exposes three CSRF-protected forms:

Action Endpoint What it runs
Create default AVD POST /emulator/avd/create-default avdmanager create avd -n vibe-default -k 'system-images;android-35;google_apis;x86_64' -d pixel_6 --force (interactive prompt auto-answered "no")
Headless launch POST /emulator/avd/launch (form name) emulator -avd <name> -no-window -no-audio -no-boot-anim detached
Stop a running emulator POST /emulator/avd/stop (form serial) adb -s <serial> emu kill

All three are audited (emulator.avd.create / launch / stop). On the slim image these still work but software emulation makes the boot a multi-minute affair without KVM.

compose.full.yml override (v0.25.0+)

docker/compose.full.yml swaps the slim image for :full and adds the host bridges needed for in-container Android emulation + in-browser mirroring:

services:
  vibe-coder-server:
    image: ${VIBECODER_IMAGE_FULL:-siamakerlab/vibe-coder-server:full}
    devices:
      - /dev/kvm:/dev/kvm
    group_add:
      - "${KVM_GID:-104}"          # match `getent group kvm | cut -d: -f3`
    ports:
      - "${VIBE_PORT:-17880}:17880"
      - "${VIBE_NOVNC_PORT:-6080}:6080"

.env:

VIBECODER_IMAGE_FULL=siamakerlab/vibe-coder-server:full
KVM_GID=104
VIBE_NOVNC_PORT=6080

Use it as an override alongside the slim compose:

curl -fsSL https://raw.githubusercontent.com/siamakerlab/vibe-coder-server/main/docker/compose.full.yml -o compose.full.yml
docker compose -f compose.yml -f compose.full.yml up -d --force-recreate vibe-coder-server

After bootup:

  1. /emulator shows "KVM (/dev/kvm) ✓ 사용 가능".
  2. Click + 디폴트 AVD 생성 (requires the system-image to have been downloaded first via /env-setup or vibe-doctor android).
  3. Click ▶ headless 시작.
  4. Open http://<host>:6080/vnc.html → Connect → emulator screen.

In-browser noVNC via reverse proxy (v0.42.0+)

Since v0.42.0 the server proxies noVNC through the same vibe_session admin authentication. The /emulator page embeds the noVNC client in an iframe pointed at /emulator/vnc/vnc.html (HTTP proxy) and /emulator/vnc/websockify (WebSocket proxy) — no host-side port 6080 exposure required.

How it works:

  • GET /emulator/vnc/{path...} forwards to http://127.0.0.1:6080/{path} inside the container. Static HTML / JS / CSS / images load same-origin.
  • WS /emulator/vnc/websockify opens a backend WebSocket to ws://127.0.0.1:6080/websockify with subprotocol binary and shuttles binary/text frames in both directions.
  • Both endpoints are admin-only (requireAdminOrRedirect on HTTP, device.userId → user.isAdmin on the WS handshake). Member and viewer sessions can't reach noVNC.
  • JDK 11+ java.net.http.HttpClient + WebSocket only. Zero new dependencies.

In compose.full.yml the ports: ["6080:6080"] block becomes optional. With the proxy, leaving 6080 inside the container is enough — and arguably safer.

Legacy: direct port-6080 (still supported)

If you'd rather skip the proxy (e.g. simpler reverse-proxy setup), the original direct exposure still works:

ssh -L 6080:localhost:6080 user@vibe-host
# browser: http://localhost:6080/vnc.html

But the proxy path is recommended on multi-user installations because of the role guard.

Roadmap

  • /emulator/vnc/ reverse proxy — landed in v0.42.0.
  • ADB log streaming on the project console for the running emulator.
  • Multi-AVD parallel sessions (one per project).

Track the work in CHANGELOG ### Phase 4 (3/n+).

Alternatives in the meantime

  • Host emulator — install Android Studio on the host, run an AVD there, ADB-connect to the container if needed (adb connect host.docker.internal:5555 after enabling ADB-over-TCP).
  • Physical device — uncomment the devices: - /dev/bus/usb:/dev/bus/usb block in compose.yml and adb devices will pick it up.
  • Cloud emulator (Firebase Test Lab, AWS Device Farm, Genymotion Cloud) — out of scope for vibe-coder; the operator runs builds + uploads manually for now.

Clone this wiki locally