The Idea and Architecture
This is Part 1 of a 6-part series on building Pockr — a single APK that runs real Docker containers on a non-rooted Android phone.
The Problem
Android phones are powerful computers. A mid-range device today has 8 cores, 8 GB RAM, and runs a full Linux kernel underneath.
Yet you cannot run a Docker container on one — not without root, not without Termux, not without jumping through hoops.
We wanted to change that. One APK. Install it. Tap Start. Run containers.
No root required. No Termux. No PC after install.
The Architecture
The stack is simpler than it sounds:
Flutter UI
└── MethodChannel (Kotlin)
└── QEMU (ARM64 binary embedded in APK)
└── Alpine Linux VM
└── Docker daemon
└── Your containers
The phone runs a real Alpine Linux virtual machine using QEMU. Inside that VM, Docker runs normally — same as on any Linux server. The app communicates with a FastAPI server running inside the VM over a forwarded port (localhost:7080).
How the Pieces Fit Together
| Layer | Technology | Notes |
|---|---|---|
| UI | Flutter (Dart) | Dark-themed dashboard, container list, settings |
| Bridge | MethodChannel | Flutter ↔ Kotlin |
| VM launcher | Kotlin ProcessBuilder
|
Spawns QEMU as a child process |
| Hypervisor | QEMU qemu-system-aarch64
|
Runs Alpine in a headless VM |
| Guest OS | Alpine Linux 3.19 | ~100 MB QCOW2 disk image |
| Container runtime | Docker (inside Alpine) | Full Docker daemon, no restrictions |
| API | FastAPI (Python 3) | Runs on 0.0.0.0:7080 inside VM |
| Auth | UUID token | Injected via QEMU kernel cmdline |
Network Architecture
QEMU's SLIRP networking provides user-mode TCP/IP without kernel modules:
Android app
└── HTTP to 127.0.0.1:7080
└── QEMU hostfwd: hostfwd=tcp::7080-:7080
└── Alpine guest eth0 (10.0.2.15):7080
└── FastAPI server
└── Docker socket
The API server must bind to 0.0.0.0:7080 — not 127.0.0.1. SLIRP delivers connections to the guest's eth0, not its loopback interface.
The QCOW2 Disk Strategy
Rather than shipping one large disk image, we use QCOW2 copy-on-write layering:
base.qcow2 (read-only, 102 MB compressed)
└── user.qcow2 (writable overlay, 8 GB virtual, starts empty)
-
base.qcow2— Alpine Linux with Docker pre-installed. Bundled in the APK. -
user.qcow2— Created fresh on first install. Persists between VM restarts. Stores pulled Docker images.
This means: pull nginx once, and it's available on every subsequent boot — no re-download.
Coming Up
-
Part 2: Why you can't just
execve()a binary on Android — and how we worked around SELinux - Part 3: Bundling 50 Termux shared libraries without breaking the ELF linker
- Part 4: Making Docker start without iptables or bridge kernel modules
- Part 5: Debugging a VM restart loop caused by a race condition
- Part 6: Firebase Test Lab results and what comes next
GitHub: github.com/AI2TH/Pockr
Pockr Series — Docker in Your Pocket
Pockr = Pocket + Docker. A single Android APK that runs real Docker containers in your pocket — no root, no Termux, no PC required.
| # | Post | Topic |
|---|---|---|
| 📖 | Intro | What is Pockr? Start here |
| 1 | Part 1 | The Idea and Architecture |
| 2 | Part 2 | Executing Binaries — The SELinux Problem |
| 3 | Part 3 | Bundling 50 Native Libraries |
| 4 | Part 4 | Docker Without Kernel Modules |
| 5 | Part 5 | Debugging the VM Restart Loop |
| 6 | Part 6 | Test Results and What's Next |
GitHub: github.com/AI2TH/Pockr
Architect & Developer: Kalvin Nathan
skalvinnathan@gmail.com · LinkedIn
Top comments (0)