How it works
Podroid is a real virtual machine, not a translation layer or a container trick. An Alpine Linux 3.23 guest running a custom Linux 7.0.5 kernel boots inside the Android app, with full process isolation and a persistent filesystem.
A real VM
When you tap "Start VM", the app launches a real hypervisor with a real aarch64 Alpine Linux guest. The guest kernel, the init system, the shell, and every process you run are all executing real ARM64 machine code inside a fully virtualized machine. There is no instruction translation of Alpine binaries, no Android kernel namespace trick, and no chroot. The guest sees a completely clean Linux environment.
Two backends
Podroid supports two hypervisor backends, selectable in Settings. The guest is identical either way - only the host-side virtualization layer differs.
- QEMU (default): software CPU emulation via TCG. Works on any ARM64 Android 9+ device. Slower than native because every guest instruction is translated and executed by the host CPU. See the Backends page for tuning options.
- AVF / pKVM: hardware virtualization via Android's Virtualization Framework. Available on recent Pixel devices that ship pKVM. Runs at near-native speed. Requires two one-time
adbpermission grants. See Backends: QEMU & AVF.
The filesystem: squashfs + ext4 overlay
The Alpine root is split across two virtual block devices so that the base system stays compact and reproducible while your changes persist across reboots.
- /dev/vdb - read-only squashfs: the complete Alpine system (packages, OpenRC services, configs) is baked into a compressed squashfs image shipped inside the APK. It is never written to. Updates to the base system ship as a new APK release.
- /dev/vda - writable ext4 image: the persistent overlay upper layer. Every file you create or modify -
apk addoutput, container images, SSH keys, home directories - lands here and survives reboots. You sized this in the setup wizard.
A minimal initramfs boots first. It mounts both block devices, stacks them with overlayfs (squashfs as lower, ext4 as upper), then calls switch_root to hand control to the real Alpine root. From that point on the guest sees a single unified filesystem, with writes silently redirected to the ext4 overlay.
An earlier version used chroot to pivot into the overlay. That broke podman exec -it: when crun calls setns(MNT) to enter a container's namespace, it resets the root, and exec'd processes saw raw kernel paths like /mnt/overlay/proc instead of /proc. switch_root reorganizes the kernel mount tree itself, so namespace forks always see a clean /.
Init system: OpenRC
OpenRC is PID 1 inside the guest. Three app-specific services on the squashfs handle bring-up:
- podroid-bootstrap: kernel modules, cgroup v2, ZRAM swap, devpts/shm/mqueue mounts, sysctl, hostname.
- podroid-network: brings up eth0, assigns 10.0.2.15/24, sets the default route and /etc/resolv.conf (SLIRP provides NAT).
- podroid-ready: emits the "Starting SSH...", "Almost ready...", and "Ready!" markers that the app tracks to update the notification and unlock the terminal.
Boot stages
The app watches the VM's serial console and updates the persistent notification as each stage marker appears. You can follow along in real time during the first boot, where host-key generation adds a visible pause between "Starting SSH..." and the next stage.
Keeping the VM alive
A foreground Android service holds a partial WakeLock for the lifetime of the VM. The WakeLock keeps the CPU running so QEMU (or AVF) is not paused by Android's power scheduler while the app is in the background. The foreground service also shows the persistent notification required by Android for any long-running background work.
On some Android builds, the OS aggressively kills child processes of apps running in the background, including the QEMU process. If your VM dies when you switch apps, see Limitations & troubleshooting.
Native binaries and page alignment
QEMU, the terminal bridge, and the Termux JNI library are compiled as ARM64 native binaries with 16 KB page alignment (-Wl,-z,max-page-size=16384). This alignment is mandatory on Android 13+ and on devices with 16 KB memory pages (some Pixel 9/10 variants). The app is arm64-v8a only; there is no 32-bit or x86 build.
Component summary
| Component | Role |
|---|---|
| Linux 7.0.5 kernel | Custom-built arm64 guest kernel with overlayfs, cgroups v2, netfilter, FUSE, and VETH built in |
| OpenRC (PID 1) | Service manager and init system inside the Alpine guest |
| squashfs (/dev/vdb) | Read-only, compressed Alpine base system; shipped inside the APK, never written to |
| ext4 overlay (/dev/vda) | Writable persistent layer; all user changes and installed packages live here |
| QEMU / AVF | Hypervisor backend; QEMU (software TCG) works on any device, AVF (hardware) on supported Pixels |
| Foreground service | Holds a partial WakeLock and the persistent notification to keep the VM running in the background |
Podroid is free software (GPL). Docs for v1.2.1. Found something inaccurate? Open an issue.