Docker Run Without iptables, bridge, or overlay2
Part 4 of 6 — building Pockr, a single APK that runs Docker on non-rooted Android.
← Part 3: Bundling 50 Native Libraries
Docker Starts — Then Immediately Fails
Once QEMU was running and Alpine booted, we launched Docker. It started, then failed silently. Containers wouldn't run. No useful error message.
The root cause: Alpine 3.19's Docker daemon defaults assume a full Linux kernel. Our QEMU kernel (6.6.14-0-virt) is stripped — it doesn't load iptables, bridge, overlay2, or ip_masq as kernel modules because there's no /lib/modules in the disk image.
What Was Missing
| Feature Docker Expects | Kernel Module | Status in Our VM |
|---|---|---|
| Network filtering |
iptables / nf_tables
|
❌ Not available |
| Container networking | bridge |
❌ Not available |
| Efficient storage | overlay2 |
❌ Not available |
| NAT/masquerade | ip_masq |
❌ Not available |
Docker's default config tries to set up all of these on startup. Every one fails with operation not supported.
The Fix: Minimal Docker Config
We bake a custom /etc/docker/daemon.json into the Alpine base image:
{
"iptables": false,
"bridge": "none",
"dns": ["10.0.2.3", "8.8.8.8", "8.8.4.4"],
"ip-masq": false,
"userland-proxy": false,
"storage-driver": "vfs"
}
| Setting | Why |
|---|---|
iptables: false |
Alpine's iptables uses nft backend — module unavailable |
bridge: none |
Bridge kernel module unavailable |
ip-masq: false |
No masquerading needed with host networking |
userland-proxy: false |
Avoids port proxy process startup failures |
storage-driver: vfs |
overlay2 needs kernel module; vfs always works |
Container Networking: --network host
With no bridge, containers run with --network host — they share the VM's network stack directly.
The VM's network is QEMU SLIRP (user-mode networking). SLIRP gives the guest:
- An eth0 at
10.0.2.15 - Default gateway at
10.0.2.2 - DNS proxy at
10.0.2.3 - Port forwarding from Android host to guest
Containers inherit all of this. They can reach the internet, pull images from Docker Hub, and serve traffic on forwarded ports.
The DNS Trap
QEMU's built-in DNS proxy at 10.0.2.3 uses UDP. On real Android hardware (confirmed on Firebase Test Lab Pixel2.arm), UDP DNS is unreliable — Docker Hub pulls fail intermittently with:
Error response from daemon: Get "https://registry-1.docker.io/v2/":
dial tcp: lookup registry-1.docker.io: i/o timeout
Fix in /etc/resolv.conf:
nameserver 10.0.2.3
nameserver 8.8.8.8
nameserver 8.8.4.4
options timeout:2 attempts:2 use-vc
use-vc forces all DNS over TCP. With multiple fallback nameservers, Docker Hub pulls work reliably — confirmed with nginx (~78 seconds on Pixel2.arm, Android 11).
Storage: vfs vs overlay2
vfs is not space-efficient (it copies layers instead of linking them), but it works without kernel modules. For our use case — a VM with an 8 GB overlay disk — it's perfectly acceptable.
Pulled images are stored in the writable user.qcow2 overlay and survive VM restarts. Once nginx is pulled, subsequent boots start it instantly with no re-download.
Next: Part 5 — Debugging a VM Restart Loop Caused by a Race Condition
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
DevOps & Systems Engineer: Kalvin Nathan
skalvinnathan@gmail.com · LinkedIn
Top comments (0)