A self-hosted, open-source alternative to Appetize.io. Launch a real Android emulator in one click, touch and type on it in real time, drag-and-drop an APK to install it, and simulate GPS, battery, and network β all from a single browser tab.
- π One-click live device β pick a phone, hit Start, and a pre-warmed emulator is streaming in seconds (no per-session boot wait).
- π Real-time touch & keyboard β native, low-latency input over scrcpy. Your laptop keyboard maps straight to the device.
- π¦ Install any APK β drag-and-drop a file or paste a download URL; install + auto-launch on the live device.
- ποΈ Hardware controls β Home, Back, Recents, Power, Volume, rotate, and screenshot from a dock beside the phone.
- π Developer tools β throttle the network (Wi-Fi β 4G β 3G β EDGE β offline), set the battery level, spoof GPS (city presets), and open any URL on the device.
- π Live dashboard β Apps library, session history, and a real-time capacity/utilization report.
- π Production-ready edge β nginx reverse proxy, automatic Let's Encrypt HTTPS, and all streams tunnelled through one secure origin (zero mixed-content).
- π¨ Polished SaaS UI β React + Tailwind, glassmorphism, teal/cyan theme, meaningful motion, fully responsive.
![]() Apps β upload, install & manage APKs |
![]() Reports β live capacity & device pool |
![]() Settings β session defaults, interface toggles & platform info |
|
The hard part of streaming a phone to a browser is doing it securely and with real input. Here's the full path of a tap:
flowchart LR
U["π Browser<br/>(React app)"] -->|HTTPS / WSS| N["nginx<br/>TLS Β· reverse proxy"]
N -->|/api| B["Backend<br/>Express"]
N -->|/stream/N| S["scrcpy-display<br/>noVNC + websockify"]
B <-->|slot pool Β· sessions| R[("Redis")]
B <-->|session records| P[("PostgreSQL")]
B -->|ADB control| E["Android Emulator<br/>QEMU + KVM Β· Android 11"]
S -->|scrcpy β Xvfb β x11vnc| E
- The backend keeps a pre-warmed pool of emulator slots in Redis. Starting a session just hands one out β no boot wait.
- Each emulator has a paired
scrcpy-displaycontainer that renders only the phone screen (scrcpy β Xvfb β x11vnc β websockify β noVNC). - nginx serves the React app, proxies the API, and tunnels each stream at
/stream/<slot>/over TLS β so the live video + WebSocket input share one secure origin (nohttp://ip:port, no mixed-content blocks). - Touches and keystrokes ride scrcpy's native input straight into Android's InputManager β real, low-latency, and keyboard-mapped.
π‘ Why scrcpy + noVNC instead of a custom WebRTC bridge? It gives native keyboard/gesture mapping and rock-solid latency with far less moving machinery. Earlier MSE/ffmpeg and ws-scrcpy approaches were prototyped and dropped β see the inline notes in
scrcpy-display/andbackend/src/routes/emulator.js.
| Layer | Technologies |
|---|---|
| Frontend | React 18, Vite 5, Tailwind CSS 3, lucide-react |
| Backend | Node.js, Express 4, Redis (slot pool + cache), PostgreSQL 15, Multer, Winston, dockerode, ws, Helmet, rate-limiting |
| Emulation | budtmo/docker-android Β· Android 11 Β· QEMU + KVM Β· GPU passthrough (-gpu host, /dev/dri) |
| Streaming | scrcpy β Xvfb β x11vnc β websockify β noVNC |
| Infra / Edge | Docker Compose, nginx, Let's Encrypt (certbot, auto-renew) |
| Observability | Prometheus + Grafana |
Default device pool: Samsung Galaxy S10 Β· Nexus 5 Β· Samsung Galaxy S6 (Android 11) β 3 pre-warmed concurrent slots.
- A Linux host with Docker + Docker Compose
- KVM enabled (
/dev/kvm) β required for usable emulator performance (a bare-metal or nested-virt VPS, e.g. Hetzner) - ~8 GB RAM for the 3-device pool
# 1. Clone
git clone https://github.com/adit9852/android-emulator-platform
cd android-emulator-platform
# 2. Configure environment (DB creds, JWT secret, etc.)
cp .env.example .env # then edit values
# 3. Build & launch the whole stack
docker compose up -d --build
# 4. Open the app
# http://<your-server-ip>The emulator pool boots once at startup and stays warm. First boot pulls images and provisions Android β give it a few minutes.
Point a domain (or use a free sslip.io host) at the server, then issue a cert with certbot's webroot challenge and enable the 443 server block in nginx/nginx.conf. The live demo runs exactly this setup with auto-renewal. See DEPLOYMENT_GUIDE.md.
All routes are under /api. Highlights:
| Method | Endpoint | Description |
|---|---|---|
GET |
/emulator/devices |
List devices and availability |
GET |
/emulator/pool |
Slot pool status |
POST |
/emulator/session |
Start a session (claims a slot) |
GET |
/emulator/session/:id |
Session details + stream URL |
DELETE |
/emulator/session/:id |
End a session (releases the slot) |
GET |
/emulator/sessions |
List active sessions |
POST |
/emulator/key |
Press a hardware key (Home/Back/β¦) |
POST |
/emulator/tap Β· /swipe Β· /text |
Inject touch / text |
POST |
/emulator/rotate/:id |
Toggle portrait β landscape |
GET |
/emulator/screenshot/:id |
Capture the screen |
POST |
/emulator/gps Β· /battery Β· /network Β· /url |
Simulate sensors / open a URL |
POST |
/upload/apk Β· /upload/apk-url |
Upload an APK (file or URL) |
POST |
/upload/install |
Install an APK on a live device |
GET / DELETE |
/upload/apks Β· /upload/apk/:id |
List / delete library APKs |
GET |
/health |
Health check |
android-emulator-platform/
βββ frontend/ # React + Vite + Tailwind SPA (the dashboard & phone view)
βββ backend/ # Express API β slot pool, sessions, input, APKs (Redis + Postgres)
βββ emulator/ # Android emulator image (budtmo-based, Android 11)
βββ scrcpy-display/ # Per-emulator screen-streaming container (scrcpy β noVNC)
βββ nginx/ # Reverse proxy + TLS termination + /stream routing
βββ monitoring/ # Prometheus + Grafana config
βββ docs/ # Screenshots
βββ docker-compose.yml
- User accounts & API keys (auth scaffolding already present)
- Horizontal scaling of the emulator pool across hosts
- Session recording & shareable replays
- More devices / Android versions
- Per-session resource quotas & idle reaping UI
- The default deployment runs 3 concurrent device slots β capacity scales with host CPU/RAM.
- The Android launcher is portrait-locked (standard Android); rotation takes effect inside apps that support landscape.
- KVM is effectively mandatory β without hardware virtualization the emulators are too slow to be usable.
Built on the shoulders of scrcpy, noVNC, budtmo/docker-android, and the wider Android emulator community. Design benchmarked against (not copied from) Appetize.io.
MIT β free to use, modify, and self-host.
docker compose up.


