Languages: English | Español
This project's official language is English. The Spanish file is provided for convenience and may not be perfectly accurate or up to date — if in doubt, this file is the source of truth.
A standalone app (frontend + backend in a single container) to manage redroid instances on any PC that only has Docker, without depending on any other infrastructure.
Work in progress — it consolidates several separate projects in one place (
redroid-manager,redroid-hwenc,redroid-nvidia, and what currently lives spread acrossjg-dashboard/plenum-redroid). Seedocs/REQUIREMENTS.md(what and why),docs/ARCHITECTURE.md(how the pieces fit together — base image, modules, and at which point of an instance's lifecycle each one is touched),docs/MODULE-AUTHORING.md(writing a module — native vs. own vs. user module, the manifest schema, the 7-stage hook contract) anddocs/ROADMAP.md(in what order) andCHANGELOG.md(tagged releases, SemVer policy) for the full picture. What exists today covers Phases 0 through 7, plus Phase 9 (port-knocking, done out of order since it does not depend on Phase 8) of the roadmap — everything in the Features table below — with Phase 8 (federation withjg-dashboard/Plenum) next. SeeCHANGELOG.mdfor the detail, tagv0.3.0.
What it does today, and what's still ahead. "Official" vs "community" below is this project's own
support-tier distinction (see docs/KNOWN-COMBINATIONS.md): official
means validated with evidence on real hardware, not a guarantee.
| Status | ||
|---|---|---|
| Instance lifecycle | ✅ | Create/start/stop/restart/delete from a web UI. Live CPU/RAM resize (no restart). Resolution/DPI/FPS/GPU-mode changes recreate the container but keep its data. |
| Hardware video encode | ✅ AMD · Intel | VA-API, validated on real Polaris/Vega/Iris Xe hardware. "Every instance uses in hardware everything the host's GPU offers" — nothing hidden behind one fixed capability tier. |
| Hardware video decode | ✅ AMD · Intel | H.264/HEVC/VP9/VP8 depending on what the host's GPU actually supports (Doctor shows which). Not recommended on AMD Polaris at the same time as hwenc — known driver instability when both run concurrently (see docs/ROADMAP.md Phase 2); encode alone is unaffected. |
| ✅ NVIDIA | Encode (NVENC) + 3D rendering (venus3d, required for gpuMode: host to boot at all on NVIDIA — see its own module README) validated end to end on real hardware: GTX 1050 Ti clean, RTX 4060 clean with a rare, transient, self-resolving texture/tiling artifact on a few frames of high-contrast screens (see redroid-nvidia's DEVLOG.md, 2026-11-09 entry) — not a blocker in real use. Decode (VA-API) confirmed bit-exact on the GTX 1050 Ti; not yet independently re-run on the RTX 4060 (see redroid-hwenc's own hardware-compatibility table). |
|
| Compatibility database | ✅ | Signed, versioned, anti-rollback database of which image+module combinations are actually validated. Checked automatically, shown per instance, never blocks — an unvalidated combo still runs, just marked "community". |
| Optional modules, with consent | ✅ | Google Apps/Play Store, ARM translation (arm64 apps on x86), fake WiFi, device profile spoofing, GPU mode — each behind an explicit contract dialog the first time it's used, never silently enabled. |
| 📝 planned | Magisk (manifest exists, no real integration yet). | |
| User-defined watchdogs | ✅ pilot | Periodic per-instance scripts (pause/resume/status from the UI, no cron to hand-edit) — CIFI (a specific game's reconnection logic) ported and running as the first one; the mechanism is generic for more. |
| Backup & restore | ✅ | Whole-instance backup/restore (tar.gz via a throwaway container), optional Android ID reset, a warning (never a block) when restoring onto an instance with different modules/GApps version. |
| Access control | ✅ | Three roles (admin/operator/viewer) with per-instance ownership. Pluggable: none (default, zero-friction), local (built-in accounts), oidc (any number of standard identity providers at once — Keycloak, Google, Plenum, another federation, ... — roles assigned here, never read from the provider), both (local accounts and OIDC on the same login screen). A local account can also be reached through a linked OIDC identity (opt-in, proven by logging into both at once — never by matching a claimed email). A setup wizard creates the first admin live from none mode, and a self-lockout guard keeps local/both from ever becoming a permanently closed door. Switching modes and adding/editing/removing OIDC providers can both be done live from the Identities panel — no .env edit needed — though a restart is still required to apply, same rule as every other setting in this app. |
| Network exposure control | ✅ | The app and every instance bind to 127.0.0.1 only, by default — redroid itself has no access control of its own. Opt into any specific interface (or all of them) per instance and for the app itself, from the UI; Docker/container-internal interfaces (docker0, br-*, veth*, ...) are filtered out of that picker by default, with an admin override. |
| Doctor | ✅ | Host diagnostics with the exact command to fix what's missing — it only diagnoses, never runs anything on its own. |
| Screen viewing | ✅ | scrcpy connector scripts (connect.sh/connect.ps1), a port for viewing from an Android device itself. |
| 📝 planned | Bundled web viewer. | |
| Federation | 📝 planned | jg-dashboard and Plenum becoming consumers of this app via oidc, reusing each host's existing identity provider instead of a separate login. |
| Port knocking | ✅ done | Per-instance, per-interface SPA (fwknop) gate for the ADB/scrcpy port, on top of the network controls above -- opened from the UI by an already-authenticated session, never by the connecting device. |
Play an Android game on Linux, with real GPU acceleration. Create an instance with gpuMode: host and GApps enabled, install the game from the Play Store, connect with scrcpy (see
"Viewing the screen from another PC"
below) and play — the same host GPU that would run a native Android emulator drives the actual
rendering, not software fallback.
Revive an old tablet or phone as a thin client. The device itself does almost nothing: it just needs a scrcpy-capable app (see ScrcpyForAndroid below) and a network path to the instance's ADB port. The instance — and everything it needs to run well — lives on whatever PC actually has the CPU/GPU for it, so hardware that's too weak to run modern Android itself is still plenty to just display one.
Connect to a TV, with an old laptop as the "console". Same idea, aimed at a living room: the
laptop only runs a scrcpy client (far lighter than running Android locally), the real instance
runs on a server with real resources, and the TV just shows whatever that laptop outputs over
HDMI. See the connector scripts below for making this a one-command launch instead of typing the
adb connect/scrcpy pair by hand every time.
Build a farm for testing or automation. Every action here goes through a plain HTTP API
(GET/POST /api/instances, …) — spin up and tear down instances from a script, run the same APK
across several Android versions, or drive an instance headlessly (no human ever opening a scrcpy
window) for a CI job or a bot. The user-defined watchdog mechanism (CIFI today) is built for
exactly this: a periodic per-instance check/action with no cron job to hand-edit per host, and the
same backup/restore mechanism that protects a personal instance's save data doubles as a way
to snapshot a known-good test environment and restore it between runs.
Images with Google Apps (hasGapps: true in the catalog) receive an Android ID (GSF) from Google
as soon as they boot. If that ID is not registered by hand at
google.com/android/uncertified within 48 hours of
the first boot, Google blocks GApps access on that instance — it is an uncertified device
(redroid + Magisk), and that registration is the way to avoid the block. It is not something the
code can do on its own, it is a human step.
The app helps make sure it does not get overlooked:
- The Instances tab shows the Android ID of every instance with GApps, with a direct link to register it and an "I already registered it" button once done.
- The Doctor tab lists as pending (
⚠️ , and ❌ if the deadline has passed) any GApps instance that is not registered.
Everything that is not 100% free software (GApps, Magisk) or that depends on a choice of the host
(fake WiFi, device profile, GPU mode) is declared with a versioned manifest (one
manifest.json per module under backend/src/modules/) instead of having its own enablement
logic.
The backend never creates or starts an instance that requires a module without a current
acceptance of its manifest — if it is missing, it answers 428 with the pending contract, the
frontend shows it (the same generic dialog for all of them, frontend/contracts.js), and only
after it is accepted is the request retried. See the Modules tab in the UI and section 5 of
docs/REQUIREMENTS.md for the full detail (it includes compatibleCon:
a module is not offered if the chosen image does not support it).
- Docker installed and its daemon running.
- A kernel with
bindersupport (standard in recent Ubuntu kernels) andbinderfsmountable at/dev/binderfs. - If you will use images with fake WiFi (
needsHwsimWifi): a loadablemac80211_hwsimmodule. - If you want GPU acceleration (
gpuMode=host): GPU drivers installed on the host.
There is no need to install Node, special sudoers entries, or anything else on the host — the
redroid-forge container itself runs --privileged --pid=host --network=host and does everything
from there.
Most images in the catalog (including the default one, redroid/redroid:15.0.0-latest) are public
on Docker Hub — the simplest path, and the one that covers almost everyone, is just:
docker pull redroid/redroid:15.0.0-latest(The Doctor tab does this check for you and shows the exact command if it is missing — you do not need to pull it by hand before starting the app, this is just here for reference.)
Only if the image you need is private/custom, or this host has no internet access, bring it over by hand instead. If it was built on another machine:
docker save <image> | gzip > image.tar.gz
# copy the file to this PC, then:
gunzip -c image.tar.gz | docker loadOr, if it is pushed to your own registry:
docker pull <your-registry>/<image>The catalog of known images lives in backend/images.json — add any new
image there (id, Docker tag, label, gpuMode, needsHwsimWifi).
git clone https://github.com/fogelmanjg/redroid-forge.git
cd redroid-forge
cp .env.example .env # check PORT is free on this host, see the file's own comments
docker compose up -d --buildThe UI is available at http://<this-pc-ip>:8080 (or the port you set in PORT, since it runs in
network_mode: host). Go to the Doctor tab first: it tells you exactly what is missing on this
host and the command to fix it before creating the first instance.
Running a second instance on a host that already has one (e.g. a disposable/dev deployment
alongside a stable one)? Clone into a differently-named directory instead of the default
redroid-forge (git clone https://github.com/fogelmanjg/redroid-forge.git my-second-forge) —
plain git clone syntax, nothing specific to this project. Each checkout is fully independent
(its own docker-compose.yml, .env, backend/data/) — just also set CONTAINER_NAME in the
second one's .env (e.g. CONTAINER_NAME=redroid-forge-prod), since the container name defaults
to the same redroid-forge for every checkout and two containers can't share one name.
Two more things a second checkout does NOT inherit from the first one, found live on server01 (10/11/2026) after each one caused a real failure there:
ADB_PORT_START/ADB_PORT_END(.env.example) must be a range that is actually free on the host — not just non-overlapping with the first checkout's own range, but with anything else listening on this host at all (a completely unrelated process can already hold a port inside the default5555-5595).docker createonly reports this as a raw "port is already allocated" once instance creation is already underway; the Doctor tab's "ADB port range availability" check now catches it ahead of time.- The optional modules' host data (gapps, arm-translation, hwsim-wifi-overlay under
backend/data/, plus anyREDROID_FORGE_*_DIR/REDROID_HWENC_ARTIFACTS_DIRoverride pointing outside it) is local, gitignored content — a fresh checkout starts with none of it, even when a sibling checkout on the same host already has it. Copy the relevantbackend/data/<module>folder(s) over from the existing checkout (or re-extract/re-provide them, see each module's own README) before creating an instance that needs them; each module fails with a clear, specific message naming the missing path/env var rather than a confusing crash, so check the instance creation error (or the Doctor tab) for exactly which one is missing.
scrcpy repository: https://github.com/Genymobile/scrcpy
Every instance exposes its ADB port on the host (adbPort in the GET /api/instances response,
the same one shown in the "ADB" column of the UI). From any other machine on the network that has
scrcpy and adb:
adb connect <server-ip>:<adbPort>
scrcpy -s <server-ip>:<adbPort>This is useful for reusing an old laptop + TV as a light "client": the laptop only runs scrcpy
(far less demanding than running Android locally), while the real instance runs on a server with
more resources. If the laptop is old and its distro ships an outdated scrcpy in its repositories
(Debian, for example, only has an old version in backports), downloading the newest official
binary from GitHub Releases is usually simpler
than compiling it — note that the scrcpy-server version must match the scrcpy client's version
exactly.
If the client you have at hand is an Android device (not a PC/laptop), there is a scrcpy port that
runs as an Android app: ScrcpyForAndroid (https://github.com/Miuzarte/ScrcpyForAndroid).
Conceptually it is the same: the app connects over the network to the instance's ADB port
(<server-ip>:<adbPort>) and decodes the video there, without needing a PC in between.
scripts/connect.sh (Linux/macOS) and scripts/connect.ps1 (Windows PowerShell) read
GET /api/instances themselves, show a menu of the running ones (or your PC's own USB-connected
devices), remember your last pick, and launch scrcpy with the flags above already applied:
scripts/connect.sh # defaults to http://localhost:8080
REDROID_FORGE_URL=http://<server-ip>:8080 scripts/connect.sh # redroid-forge on another machinepowershell -File scripts\connect.ps1There is no bundled web-based screen viewer yet — a planned feature, not shipped today. Until then, the connector script plus a scrcpy client is the whole dependency: no Caddy, no reverse proxy, no auth stack to set up on redroid-forge's side. That's not a statement that those are incompatible with it — a reverse proxy or auth layer in front of redroid-forge is entirely your own choice to run, redroid-forge just doesn't set one up or manage one for you. If you want a nicer interactive client instead of (or alongside) these scripts, take a look at scrcpy-manager (a terminal UI with mouse support, saved connection profiles, and multiple live scrcpy sessions at once) — a separate project we don't bundle, but one of its ideas (recovering a fixed ADB port after a device reboot) is reflected in these scripts.
First time hitting a scrcpy problem (no audio, choppy video, wrong bitrate)? See
docs/SCRCPY-TIPS.md — the handful of gotchas that actually come up, plus
the shortcuts worth knowing, without the full manual.
backend/src/server.js Express, serves /api/* and the static frontend
backend/src/lib/ dockerRuntime, binder, hwsimWifi, androidIdentity, doctor, store,
portAllocator, moduleManifests, moduleAcceptance, moduleGate, hwAccel,
knownDb*, permissions, userStore, session, oidc, auth, accountLinks,
roleAssignments, quotas, networkInterfaceFilter, settings
backend/src/routes/ instances, doctor, images, modules, db, settings, backups, network, auth
backend/src/modules/ manifests/*.json (each module's contract, section 5 of REQUIREMENTS.md)
+ one folder per real module (hwenc/, hwdec/, gapps/,
arm-translation/, wifi-interno/, redroid-gralloc-fix/, cifi/):
integration script, not just a manifest
backend/native/ vaapi-daemon — host daemon of the hwenc/hwdec modules (see docs/ARCHITECTURE.md)
backend/db/ bundled seed of the known-combinations database + trusted signing keys
backend/images.json catalog of the available redroid images
backend/data/ persistent state (instances.json, settings.json, users.json, ...)
backend/test/ tests (node --test, no new dependencies)
android/ Codec2 components (hardware encoder/decoders) and their test tools
frontend/ vanilla frontend (no build step); contracts.js = generic contract modal,
authUi.js = login gate + per-capability UI hide/disable (Phase 7)
scripts/ connect.sh / connect.ps1 -- scrcpy connector scripts for end users (not
backend/scripts/, which is dev/admin-time tooling)
docs/ REQUIREMENTS.md (what and why), ARCHITECTURE.md (how the pieces fit
together), MODULE-AUTHORING.md (writing a module), KNOWN-COMBINATIONS.md,
ROADMAP.md (in what order) and SCRCPY-TIPS.md (client-side troubleshooting)
CHANGELOG.md tagged releases, SemVer policy (English only, same reasoning as ROADMAP.md)
This project is a frontend/manager on top of what other projects have already built — not a replacement or a competitor. Explicit acknowledgement:
- redroid (remote-android) — the Android-in-Docker itself that all of this runs on. Apache License 2.0 (kernel modules under GPL v2).
- The logic of
binder.jsandhwsimWifi.jsis ported fromplenum-redroid(host-resource-allocator.service.tsandfake-wifi-networking.service.ts), simplified: without the coexistence assumptions withjg-dashboard v1(fixed port offset, hwsim fallback disabled) that do not apply in a single-purpose deploy like this one.
Apache License 2.0 — see LICENSE.