Android Docker Host
This configuration runs Docker Engine on a rooted arm64 Android device. It does not mean generic Android support: kernel, mount, storage, and networking requirements must be checked individually.
This requires more host work than a normal Linux server. Verify not only the Docker CLI, but container startup, outbound traffic, published ports, and persistence.
For the full procedure from start to finish, see Android Complete Guide. This page is the shorter entry point for identifying which case your device falls into.
Layout
Android arm64 device
├─ Android OS / vendor kernel
│ ├─ root (KernelSU / Magisk etc.)
│ ├─ container-oriented kernel config
│ ├─ Android netd / policy routing
│ └─ SELinux
├─ Termux
│ ├─ BusyBox / SSH / tmux
│ └─ fetch Ubuntu rootfs
├─ Ubuntu rootfs (real chroot)
│ ├─ Docker Engine / containerd / runc
│ └─ Compose
├─ ext4 loop image → /var/lib/docker
└─ containers
└─ vyline
optional:
Portainer / Tailscale / Cloudflare Tunnel etc.proot-distro is useful for downloading the rootfs, but Docker Engine itself runs in a real chroot. Portainer and remote-access tools are not Vyline requirements.
Preflight
uname -m
id
getenforce
cat /proc/cgroups
mount | grep -E 'cgroup|cgroup2'
ls -l /dev/block/loop* 2>/dev/nullIn addition to arm64 and root, check namespaces, cgroups, seccomp, OverlayFS, veth/bridge, netfilter, loop/ext4, and other Docker kernel requirements. See Android Kernel for details.
| What happens | Where to go next |
|---|---|
| dockerd itself does not start | Check Kernel, storage, and cgroups |
hello-world works but the container cannot reach the Internet | Networking |
Internet works but LAN cannot reach :3000 | Check published-port and nft/legacy packet paths |
| Docker and networking both work | Proceed to Vyline Compose |
Termux
pkg update -y
pkg install -y proot-distro busybox openssh tmux coreutils curl
termux-wake-lock
proot-distro install ubuntuTypical Ubuntu rootfs location:
/data/data/com.termux/files/usr/var/lib/proot-distro/containers/ubuntu/rootfs
Mounts for the real chroot
PREFIX=/data/data/com.termux/files/usr
ROOT="$PREFIX/var/lib/proot-distro/containers/ubuntu/rootfs"
BB="$PREFIX/bin/busybox"
su
$BB mount --bind "$ROOT" "$ROOT"
$BB mount --make-rslave "$ROOT"
$BB mount --rbind /dev "$ROOT/dev"
$BB mount --make-rslave "$ROOT/dev"
$BB mount -t proc proc "$ROOT/proc"
$BB mount --rbind /sys "$ROOT/sys"
$BB mount --make-rslave "$ROOT/sys"
$BB mount -t tmpfs -o mode=755,nosuid,nodev tmpfs "$ROOT/run"Self-binding the rootfs and making it rslave allows runc's recursive mount operations to work on configurations that otherwise fail with errors such as remount / ... invalid argument.
Move Docker data-root to ext4 only when needed
First test whether overlay2 works on the Android filesystem. Only devices that hit EINVAL with combinations such as F2FS, casefold, and the vendor kernel need to move /var/lib/docker to an ext4 loop image.
stat -f -c '%T' /data
docker info 2>/dev/null | grep -E 'Storage Driver|Backing Filesystem'mkdir -p /data/local/docker-storage
truncate -s 64G /data/local/docker-storage/docker-ext4.img
/system/bin/mke2fs -t ext4 -F /data/local/docker-storage/docker-ext4.img
LOOP=$(/system/bin/losetup -f)
/system/bin/losetup "$LOOP" /data/local/docker-storage/docker-ext4.img
$BB mount -t ext4 -o rw,noatime "$LOOP" "$ROOT/var/lib/docker"
Docker inside the chroot
{
"storage-driver": "overlay2",
"exec-opts": ["native.cgroupdriver=cgroupfs"],
"log-driver": "local"
}rm -f /var/run/docker.pid
nohup dockerd >/var/log/dockerd.log 2>&1 </dev/null &
docker run --rm hello-world
docker run --rm alpine uname -m
docker run --rm alpine ping -c 1 1.1.1.1
Networking is a separate problem
Even with Docker running, Android netd policy routing or a legacy/nft mismatch can break container-to-Internet traffic or LAN access to published ports. Follow Android Networking for diagnosis.
Vyline Compose
services:
vyline:
image: ghcr.io/tqmane/vyline:latest
pull_policy: always
platform: linux/arm64
ports:
- "3000:3000"
volumes:
- /opt/vyline/data:/app/data
- /opt/vyline/storage:/app/storage
environment:
VYLINE_HOST: 0.0.0.0
PORT: 3000
VYLINE_DATA_DIR: /app/data
VYLINE_STORAGE_DIR: /app/storage
VYLINE_LAN_ACCESS: "false"
VYLINE_TRUST_REMOTE_OWNER: "false"
TZ: Asia/Tokyo
restart: unless-stoppedBecause the image is multi-arch, platform: linux/arm64 is normally optional. Keep it only if you want to be explicit. Use the standard health check first; only on Android chroots where host-side HTTP works but the healthcheck/exec path is broken should you add:
healthcheck:
disable: trueEven then, verify Vyline itself with curl http://127.0.0.1:3000/healthz before disabling it. See the complete guide for the case where only docker exec has a broken mount namespace.