Browser Android emulator
Android inside Docker — redroid on Apple Silicon (Colima), or budtmo/noVNC on Linux+KVM. adb from the host; companion over reverse.
Twig is Android-only. This repo runs Android inside Docker so you (or a coding agent) can install, debug, and pair the companion without a physical phone.
Browser UI ──► Docker Android ◄── host adb (:5555)
│
└── adb reverse :8787 ──► twig companionTwo profiles
| Profile | When | Container | Browser UI |
|---|---|---|---|
| mac (default on Darwin) | Apple Silicon / no KVM | Android 16 redroid (twig/redroid:16…-patched) via Colima | ws-scrcpy on :8000 |
| linux | Linux host with /dev/kvm | budtmo/docker-android:emulator_14.0 | noVNC on :6080 |
bun run emu:doctor
bun run emu:prepare # Colima + binderfs on Mac; KVM check on Linux
bun run emu:up
bun run emu:wait && bun run emu:adb
bun run emu:openForce a profile: TWIG_EMU_PROFILE=mac|linux.
Apple Silicon (this is the Mac path)
- Colima provides a Linux VM; Docker runs there.
emu:prepareloadsbinder_linuxand mounts/dev/binderfs(required by redroid).- Redroid boots Android 16 (arm64-only).
emu:preparebuilds a small libcutils patch so memfd works on Colima kernels that lack/dev/ashmem(stock redroid 16 otherwise crash-loops — see redroid-doc#869). haris132/ws-scrcpymirrors the screen in the browser at http://localhost:8000 (best-effort; prefer desktopscrcpyif the browser UI flakes).
brew install colima docker docker-compose
brew install --cask android-platform-tools
colima start --cpu 4 --memory 8 --disk 60 --vm-type vz
bun run emu:prepare
bun run emu:up
bun run emu:open # http://localhost:8000Desktop fallback: brew install scrcpy && scrcpy -s localhost:5555.
Linux + KVM (noVNC)
sudo apt install qemu-kvm adb && kvm-ok
TWIG_EMU_PROFILE=linux bun run emu:up
bun run emu:open # http://localhost:6080/?autoconnect=trueInstall Twig onto the container
Expo Go will not work (sqlite-vec needs a real build). Wait until sys.boot_completed=1
and service check package reports found (right after emu:up, package/activity may be
missing for a minute while zygote starts).
cd apps/native
bunx expo prebuild --platform android
# Expo's device picker breaks on redroid's emulator-5554 alias — build then adb install:
export JAVA_HOME="$(brew --prefix openjdk@17)" # JDK 26 breaks AGP
cd android && ./gradlew :app:assembleDebug
adb -s localhost:5555 install -r app/build/outputs/apk/debug/app-debug.apk
adb -s localhost:5555 shell am start -n org.bhalala.twig/.MainActivity
bunx expo start --dev-client # Metro for JSOr: bun run emu:install path/to/app-debug.apk
Bun + native build: root bunfig.toml sets linker = "hoisted" so CMake does not see
node_modules/.bun/pkg@ver/ paths. If apps/native/node_modules still has stale .bun
symlinks, wipe nested modules and reinstall:
rm -rf apps/*/node_modules packages/*/node_modules && bun installCompanion link (simulated USB)
adb reverse works over TCP the same as a cable:
bun run emu:wait && bun run emu:adb
bun run companion
# In the Android UI: Twig → Laptop → enter the six-digit codePorts
| Port | What |
|---|---|
| 5555 | ADB |
| 8000 | Browser UI (mac / ws-scrcpy) |
| 6080 | Browser UI (linux / noVNC) |
| 9000 | Container logs (linux profile) |
| 8787 | Twig companion WebSocket (host), reversed into Android |
If the browser screen is blank
Usually the Android container never finished booting, so there is no display compositor for ws-scrcpy to mirror.
Symptoms:
adb shell wm size→Can't find service: windowgetprop sys.boot_completedempty- Browser UI loads, but stream fails / stays black
Fix:
# Use Android 14 (16 often stalls under Colima)
bun run emu:down
# wipe half-booted data
docker volume rm twig-android_twig-redroid-data 2>/dev/null || true
bun run emu:prepare
bun run emu:up
bun run emu:wait
adb -s localhost:5555 shell wm size # should print Physical size: 1080x1920Browser mirror (http://localhost:8000) is best-effort — the packaged ws-scrcpy server can choke
on newer Android clipboard APIs. Reliable viewer on the Mac:
brew install scrcpy
scrcpy -s localhost:5555 --no-audiobun run emu:doctor
bun run emu:up && bun run emu:wait && bun run emu:adb
adb -s localhost:5555 shell getprop ro.build.version.release- Prefer
adb -s localhost:5555so aliases likeemulator-5554do not confuse tooling. - Pairing still needs a human (or browser operator) for the six-digit code.
docker/android/compose.yml+scripts/emuare the source of truth.