adb only needs plain outbound TCP to the TV, so it runs fine in a container with normal bridge networking (no host networking or privileged mode). Bundles adb into the image via Dockerfile, adds docker-compose.yml with a persisted data/ volume, and documents two ways to fold this into another docker-compose-based repo (vendored build context vs. a published image). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSVyYcrjrbmrMKT85G4fyn
TV ADB Remote
A slick, installable web remote for Android TV, controlled over ADB. Run a small Node server on any always-on machine on your home network (a Pi, NAS, old laptop, home server); point your phone's browser at it and control the TV from a mobile-optimized PWA.
Why a local web app instead of a native Android app: Android TV only
exposes control over ADB once you enable network debugging, and the
official ADB tooling is a desktop program. Rather than reimplement the ADB
protocol inside a phone app, this runs adb on a machine that already has
it, and serves a touch-friendly remote UI to any phone/tablet browser on the
LAN. Add to the home screen and it behaves like an installed app.
Features
- Power, mute, input switch, volume rocker, channel rocker
- D-pad navigation, OK, back, home, menu
- Media transport (rewind / play-pause / fast-forward)
- Text entry (e.g. search boxes) sent as real keystrokes
- One-tap launch for common streaming apps (Netflix, YouTube, Prime Video, Disney+, Spotify, Plex)
- Manage multiple TVs, each remembered with a friendly name
- Built-in pairing flow for Android 11+ ("Wireless debugging")
- Installable PWA with offline app shell
Requirements
- Docker and Docker Compose on any always-on machine on your home network (a Pi, NAS, old laptop, home server).
- The TV and that machine on the same local network.
adb does not need to run on bare metal — it's a plain TCP client that
dials out to the TV's IP once network/wireless debugging is on, so it works
fine from inside a container with normal bridge networking. No host
networking, USB passthrough, or privileged mode required. The container
image bundles adb itself, so nothing else needs installing on the host.
Set up the TV
- On the Android TV: Settings → System (or Device Preferences) → About → tap "Build" repeatedly to enable Developer options.
- Settings → Developer options → turn on Network debugging (older Android TV) or Settings → System → Developer options → Wireless debugging (Android 11+ / Google TV).
- Note the IP address shown (Settings → Network & Internet).
- Android TV ≤ 10: that's it — the debug port (5555) is open.
- Android 11+ / Google TV: it shows a separate pairing IP:port and a 6-digit code. Use the app's "Pair a new Android 11+ TV" flow, then add the TV using its regular IP (still port 5555 once paired).
Run the server
docker compose up -d --build
This serves the app at http://<this-machine's-LAN-IP>:8787. Open that URL
on your phone, add your TV by IP in Settings (⚙), then optionally "Add to
Home Screen" for a full-screen, app-like remote.
Saved TVs persist in ./data/devices.json on the host (bind-mounted into
the container), so they survive docker compose down/rebuilds. Override the
port by changing the PORT environment variable and the left side of the
ports mapping in docker-compose.yml.
Adding this as a service in another docker-compose stack
If you're folding this into a larger compose project rather than running it standalone, you have two straightforward options:
- Vendor the source (git submodule or subtree at, say,
services/tv-adb-remote/) and point that stack's compose file at it:services: tv-adb-remote: build: ./services/tv-adb-remote ports: - "8787:8787" volumes: - ./services/tv-adb-remote/data:/app/data restart: unless-stopped - Build and push an image (e.g. to GHCR or Docker Hub) from this repo's
CI, then reference it by tag instead of a build context — no vendored
source in the other repo at all:
This fits a "docker nearly exclusively" stack best if most of its other services are already referenced as prebuilt images rather than local build contexts — ask if you want a GitHub Actions workflow added here to publish the image on push.
services: tv-adb-remote: image: ghcr.io/<you>/tv-adb-remote:latest ports: - "8787:8787" volumes: - ./tv-adb-remote-data:/app/data restart: unless-stopped
Either way, the only two things the other repo's compose file needs to get
right are the port mapping and a persistent volume for /app/data.
Development
cd server
npm install
npm test
Runs the server's unit tests (command construction, input validation, device persistence) using Node's built-in test runner — no real TV, and no Docker, required. Useful for iterating on server code before rebuilding the image.
Project layout
Dockerfile Bundles adb + the server into one image
docker-compose.yml Standalone run config (port 8787, data/ volume)
server/ Express app + ADB bridge (source of truth for control logic)
src/adb.js Thin wrapper around the `adb` CLI (connect/pair/keyevent/…)
src/keycodes.js Android KeyEvent constants used by the remote buttons
src/deviceStore.js Persists saved TVs to data/devices.json
src/index.js HTTP API + static file serving
test/ Node test runner specs
public/ The PWA served to phones (HTML/CSS/vanilla JS, no build step)
scripts/ One-off dev scripts (icon generation)
Security note
This server has no authentication and will happily run adb commands
against whatever IP you give it — it's designed to sit on your trusted home
LAN, not the public internet. Don't port-forward it.