· 6 min read ·

From ptrace to Namespaces: What Podroid Gets Right About Rootless Containers on Android

Source: hackernews

The Linux-on-Android ecosystem has been shaped for years by a single tool: proot. Every major project that lets you run a Linux distribution on an unrooted Android phone, UserLAnd, Andronix, anLinux, is built on top of it. proot works, and it works for a lot of people, but it is a simulation built on ptrace rather than a real container runtime. Podroid takes a different approach by bringing Podman to Android using Linux user namespaces directly, which means genuine kernel-level isolation instead of a watching process intercepting every syscall.

The distinction matters more than it might appear at first.

How proot actually works

proot intercepts syscalls using the same ptrace mechanism that debuggers use. When a proot-managed process makes any system call, the kernel suspends it and notifies the proot process, which inspects the call, rewrites any filesystem path arguments to redirect them into the chroot environment, and then allows execution to resume. For UID-related calls, proot simply lies: getuid() and getgid() return 0 regardless of the real user identity, because proot intercepts those too.

This is functional but carries a real overhead. Every single syscall, including the high-frequency ones like read, write, stat, and open, goes through the intercept-inspect-rewrite cycle. For interactive shell use the penalty is invisible, but for filesystem-intensive workloads like compiling code, running apt or pip, or doing anything that generates many small file operations, the slowdown is measurable. A benchmark environment that is calling open thousands of times a second will spend a significant portion of its time in context switches between the tracee and proot itself.

More fundamentally, proot does not create kernel namespaces. There is no real filesystem isolation, no network namespace, no PID namespace. The appearance of a separate Linux environment is maintained entirely by a user-space process that happens to be watching. The moment proot dies, the illusion collapses.

Linux user namespaces: the actual mechanism

User namespaces were introduced in Linux 3.8 and progressively made more capable across subsequent releases. The core idea is that an unprivileged process can create a namespace in which it has UID 0, root, without actually having elevated privileges on the host. The mapping is defined via /proc/[pid]/uid_map and /proc/[pid]/gid_map. A mapping that says “UID 0 inside this namespace corresponds to UID 10000 on the host” is enforced by the kernel itself. From inside, the process sees root. From outside, the kernel tracks an unprivileged user.

This is not syscall interception. The kernel enforces the boundary natively, and no watching process is required. Combine a user namespace with a mount namespace, a network namespace, and a PID namespace, and you have the full container isolation model without any elevated privileges.

Podman was designed specifically to exploit this. Unlike Docker, which traditionally runs a privileged daemon that all clients communicate with over a socket, Podman is daemonless and supports rootless operation as a first-class feature. Rootless Podman uses newuidmap and newgidmap, setuid helper binaries that apply UID/GID mappings based on /etc/subuid and /etc/subgid, to configure the user namespace mapping without requiring the container runtime itself to be privileged.

Networking in rootless mode is handled by slirp4netns, which creates a user-space TCP/IP stack attached to the container’s network namespace. The container sees a normal network interface; on the host, all traffic routes through slirp4netns without any privileged socket operations. Latency is slightly higher than native networking because of the user-space stack, but for development and general use the difference is not significant.

For storage, rootless Podman historically fell back to fuse-overlayfs, a FUSE-based implementation of the overlay filesystem that runs in user space. Kernel-native overlayfs requires privilege in older kernels; support for unprivileged overlay inside user namespaces arrived in Linux 5.11. On devices running a recent enough kernel, native overlay becomes available and fuse-overlayfs is no longer needed.

Android’s kernel and the namespace question

Android runs a Linux kernel, and modern Android releases have been pushing kernel versions upward. Android 12 standardized on Linux 5.10 for new devices, Android 13 on 5.15, and Android 14 and 15 have seen 6.1 and later on newer hardware. The kernel version provides the primitives, but what determines whether user namespaces actually work on a given device is the combination of kernel configuration and SELinux policy.

Android’s SELinux policy is unusually strict compared to desktop Linux distributions. The Android security team has historically restricted unprivileged user namespace creation because namespaces can be used to exploit certain kernel vulnerabilities that would otherwise be mitigated by privilege requirements. This is a real tradeoff: user namespaces expand the kernel attack surface, and Android’s threat model prioritizes strict app sandboxing. Many OEM builds, particularly Samsung’s OneUI-based devices, apply additional kernel hardening and SELinux restrictions that block namespace creation entirely from userspace applications.

For Podroid to work, the device needs to have user namespaces enabled and not blocked by policy. This tends to be the case on Google Pixel devices running stock firmware and on devices running AOSP-derived custom ROMs. It is less reliably true on locked-down OEM builds. This is not a flaw in Podroid’s design; it is a reflection of a genuine kernel configuration difference between devices.

What Podroid provides in practice

Podroid ships Podman and its dependency stack configured for Android. Once set up, you interact with it the same way you would on a desktop Linux machine:

podroid pull alpine
podroid run -it alpine sh
podroid run -d -p 8080:80 nginx

The container runs in a real network namespace, a real mount namespace, and a real PID namespace. The isolation is kernel-enforced. You can run multiple containers simultaneously, manage them with the standard Podman command set, use volume mounts, and expose ports through slirp4netns. This is the same container model used in production infrastructure, running on a phone without root.

The performance difference from proot is most visible in workloads that stress the filesystem: installing packages inside the container, compiling source code, running test suites. Without the ptrace overhead on every syscall, these operations run at speeds much closer to native execution.

Prior art and where Podroid fits

Running real containers on Android has historically required root. Linux Deploy can bootstrap full Debian or Ubuntu environments but needs root to mount loopback filesystems and configure chroot properly. Docker on rooted Android, documented in various forms over the years, similarly requires root and a compatible kernel build.

The UserLAnd and Andronix projects democratized Linux-on-Android for unrooted devices by accepting proot’s tradeoffs. Termux itself provides a genuine native Linux userspace on Android without proot, but it is not containerized. Everything runs in the same Termux filesystem context; there is no isolation between different environments, and running a full distribution inside it means layering proot back in.

Podroid occupies a different position: it offers real container isolation without root, but requires a device where the kernel and SELinux policy permit unprivileged user namespaces. The compatibility surface is narrower than proot-based solutions, which work almost everywhere because ptrace is universally available. On devices where Podroid works, the experience is qualitatively different, not just faster but architecturally sound.

The broader significance

What Podroid demonstrates is that the Android kernel, on sufficiently modern and permissive devices, has matured to the point where the user-space primitives required for rootless containerization are available. The gap between a server running Podman and an Android phone running Podroid is now primarily a policy gap rather than a kernel capability gap.

That is a meaningful shift. The proot era established that running Linux on Android without root was possible; the user namespace era, which Podroid is an early concrete example of, suggests that running Linux containers on Android without root can be done the right way. Whether the SELinux policy trajectory in Android moves toward or away from permitting this will determine how broadly projects like Podroid can eventually reach.

Was this interesting?