Linux containers on Android. No root required.
A real Alpine Linux VM, a real Linux kernel, and rootless Podman, all in a single APK.
Website · Download · Quick Start · Architecture · Build
Termux gives you a Linux shell on Android, but it runs on the host kernel, and the namespaces, cgroups, and netfilter rules that real containers need are not there. Google's new Terminal app uses the Android Virtualization Framework, which is locked to recent Pixel devices and ships without a container runtime. proot is a chroot emulation; most Docker Hub images do not work reliably.
Podroid runs an actual Alpine Linux VM with its own purpose-built Linux 6.6 kernel. Every OCI image runs unmodified, every podman flag works, and your changes (apk add, rc-update add, custom configs) persist across reboots in a writable overlay.
| Podroid | Termux + proot | Google Terminal | KVM / chroot | |
|---|---|---|---|---|
| Real Linux kernel | ✅ (custom 6.6) | ❌ | ✅ (Debian) | depends |
| Rootless Podman | ✅ | ❌ manual | ✅ | |
| Works on stock Android 9+ | ✅ | ✅ | ❌ (Pixel only) | ❌ (root + recovery) |
| OCI images run unmodified | ✅ | ❌ | ✅ | |
| Persistent VM state | ✅ | n/a | ✅ | ✅ |
| Zero post-install setup | ✅ | ❌ | ❌ | ❌ |
|
|
|
|
|
|
- Download the latest APK from the Releases page.
- Install and open Podroid. The first launch extracts the kernel, initramfs, and Alpine squashfs to app storage (~100 MB, one-time).
- Tap Start VM. Boot completes in 6–15 seconds depending on device.
- Tap Open Terminal when the indicator turns to Ready.
# Hello world
podman run --rm alpine echo hello
# Web server, accessible from Android browser at http://127.0.0.1:8080
podman run -d -p 8080:80 nginx
# Rootless interactive
adduser -G wheel dev # then: doas su - dev
podman run --rm -it alpine shDefault credentials: root / podroid. Change with passwd. Create a regular user with adduser -G wheel <name>; wheel-group members can use doas and sudo.
| Architecture | ARM64 only (aarch64) |
| OS | Android 9.0+ (API 28) · target API 36 |
| Storage | ~200 MB app + chosen VM disk size (default 2 GB, max 64 GB) |
| Memory | 2 GB device RAM recommended (the VM defaults to 512 MB allocation) |
┌──────────────────────────────────────────────┐
│ Android App │
Compose UI │ ┌──────────────┐ ┌────────────────────┐ │
Setup ▸ Home ▸ │ │ PodroidQemu │ │ PodroidService │ │
Terminal ▸ Settings │ │ (engine) │ │ (foreground + WL) │ │
│ └──────┬───────┘ └─────────┬──────────┘ │
└─────────┼────────────────────┼───────────────┘
│ │
spawns │ │ owns notification
▼ ▼
┌──────────────────────────────────────────────┐
│ QEMU 11.0.0-rc2 (TCG, ARMv8 virt machine) │
└──┬─────────────────┬─────────────────┬───────┘
serial.sock │ terminal.sock │ ctrl.sock │ qmp.sock
(boot) │ (hvc0) │ (hvc1) │ (control)
▼ ▼ ▼
┌─────────────────────────────────────────────┐
│ Alpine Linux 3.23 VM │
│ /sbin/init (busybox) ▸ /etc/inittab ▸ │
│ OpenRC (PID 1) │
│ │
│ /dev/vda ext4 overlay (writable) │
│ /dev/vdb squashfs (read-only base) │
│ │
│ podroid-bootstrap podroid-network │
│ podroid-resize dropbear (optional) │
│ podroid-ready ▸ "Ready!" marker │
│ │
│ Podman + crun + netavark + slirp4netns │
└─────────────────────────────────────────────┘
- PodroidApplication copies kernel, initramfs, and Alpine squashfs from APK assets into
filesDiron first launch (size-checked, idempotent). - QEMU launches with
-M virt, two virtio-blk drives (/dev/vdawritable ext4,/dev/vdbread-only squashfs), four Unix sockets, and the user-configured RAM / CPU count. init-podroid(45 lines, runs in initramfs as PID 1) mounts the squashfs as the lower layer of an overlayfs, the ext4 image as the upper, andswitch_roots into/sbin/init.- OpenRC brings up the system via three custom services on the squashfs:
podroid-bootstrap: cgroup v2, devpts/shm/mqueue, sysctl, ZRAM, mount-propagation tweaks for rootless containerspodroid-network: eth0 up,10.0.2.15/24,/etc/resolv.confpodroid-ready: emits the boot-stage markers consumed by the Android UI
- Android UI sees
Ready!onserial.sock→ state = Running → the bridge connects directly to virtio-console (/dev/hvc0) and the terminal becomes interactive.
Why
switch_rootand notchroot?setns(CLONE_NEWNS)insidecrun execresetsfs->root, so a chroot pivot leftpodman exec -itlooking at raw kernel paths and failing to open/dev/ptmx.switch_rootreorganizes the kernel mount tree itself, so namespace operations land on a clean/. (fix details)
Podroid/
├── app/ Android application (Jetpack Compose, Hilt)
│ └── src/main/
│ ├── java/com/excp/podroid/
│ │ ├── engine/ PodroidQemu, QmpClient, VmState
│ │ ├── service/ PodroidService (foreground)
│ │ ├── data/repository/ DataStore-backed settings & port forwards
│ │ └── ui/ Compose screens + theme
│ ├── jniLibs/arm64-v8a/ QEMU, podroid-bridge, libslirp, libtermux
│ └── assets/ kernel, initramfs, squashfs, fonts, themes
├── init-podroid Minimal initramfs script (~45 lines)
├── podroid-bridge.c Native PTY ↔ virtio-console relay
├── Dockerfile Kernel + initramfs + QEMU build pipeline
├── build-rootfs/ Alpine squashfs build pipeline
│ ├── Dockerfile.rootfs
│ ├── build-rootfs.sh
│ └── files/ OpenRC services + helpers baked into the squashfs
├── build-all.sh Unified build / deploy script
├── podroid_kernel.config Custom kernel Kconfig fragment
└── docs/ GitHub Pages site (extv.github.io/Podroid)
| Binary | Purpose |
|---|---|
libqemu-system-aarch64.so |
QEMU TCG engine (ELF executable shipped as a .so) |
libpodroid-bridge.so |
PTY ↔ virtio-console relay with debounced SIGWINCH |
libtermux.so |
Termux terminal emulator JNI (rebuilt for 16 KB pages) |
libslirp.so |
SLIRP user-mode networking, statically linked into QEMU |
All native binaries are built with -Wl,-z,max-page-size=16384 for compatibility with 16 KB-page Android devices (mandatory on Android 13+).
- Docker 20.10+ (for kernel, initramfs, rootfs, and QEMU builds)
- Android NDK r27c (for the bridge and Termux native libs)
- Android SDK with platform 36 + build-tools
git clone https://github.com/ExTV/Podroid.git
cd Podroid
./build-all.sh kernel # ~5–10 min, Docker-cached
./build-all.sh initramfs # kernel + minimal initramfs
./build-all.sh rootfs # Alpine squashfs (~30 s, Docker-cached)
./build-all.sh qemu # ~30 min first run, fully cached after
./build-all.sh termux # local NDK build of libtermux.so
./gradlew installDebug # build + install the APK on a connected deviceOr just:
./build-all.sh all # everything above, in order
./build-all.sh deploy # build + install + launch
./build-all.sh test # boot validation: deploys APK, polls console.log for "Ready!"adb logcat -s PodroidQemu # engine logs
adb shell run-as com.excp.podroid.debug cat files/console.log # full VM serial log
ssh root@127.0.0.1 -p 9922 # if SSH is enabled (password: podroid)- Zero-config Docker and LXC (in addition to Podman) ✅ 1.1.7
-
/proc/config.gzshipped + complete container-kernel feature matrix ✅ 1.1.7 - OpenRC as PID 1:
apk add docker; rc-service docker startworks and persists ✅ 1.1.6 - Adaptive layouts for tablets and landscape phones ✅ 1.1.6
- Issue #17:
podman exec -itworks rootful and rootless ✅ 1.1.6 - Custom Linux 6.6 kernel with all required options forced built-in ✅ 1.1.4
- virtio-console terminal channel separate from boot serial ✅ 1.1.4
- User-loadable terminal fonts (Quick Settings → font picker →
+ Addchip → pick any.ttfvia the system file chooser; long-press a custom font to remove) ✅ next
Have an idea? Open an issue.
Pull requests are welcome. Before opening a PR, please read CONTRIBUTING.md and skim skill.md. It documents the boot pipeline, every native binary, and the design quirks you need to know to make changes that don't regress.
Found a bug? Please include the contents of console.log:
adb shell run-as com.excp.podroid.debug cat files/console.logIf you're using Claude Code, Cursor, Copilot, or another assistant, point it at skill.md before making any change. It covers the full architecture, every constant, every quirk, and every common task.
Podroid stands on the shoulders of giants:
| Project | Role |
|---|---|
| QEMU | Machine emulation, the heart of the VM |
| Linux | Custom 6.6.87 kernel |
| Alpine Linux | Tiny, fast Linux distribution |
| Podman · crun | Rootless container runtime |
| Termux | Terminal emulator engine |
| Limbo PC Emulator | Original groundwork for QEMU on Android |
See CREDITS.md for the full list of upstream projects, libraries, and contributors.
Podroid is released under the GNU General Public License v2.0. QEMU, the Linux kernel, Alpine, and Podman are each distributed under their respective upstream licenses.