Run Real Docker on Android — No Root, No Tricks, Just QEMU
You have an old Android phone in a drawer. It has 8 cores, 8–12 GB of RAM, and a fast SSD. It’s collecting dust because the screen is cracked or the battery is tired. Meanwhile, you’re paying $5/month for a 1 GB VPS to run the same Docker containers you could run on that phone for free.
The problem: Android doesn’t run Docker. Docker needs the Linux kernel’s cgroups, namespaces, and overlay filesystem. Android’s kernel has those features compiled out for unprivileged apps, and there’s no way to add them back without rooting the device.
The workaround everyone recommends is udocker or proot-distro. Those work for some things — pulling an image and chrooting into it — but they don’t give you a real Docker daemon. You can’t docker build. You can’t run docker compose with networking. You can’t use any tool that expects the Docker API to actually behave like Docker.
This tutorial shows the only path I’ve found that gives you a real Docker daemon running real containers on a non-rooted Android phone: run Debian in a QEMU virtual machine inside Termux. It’s slow (10–25× overhead from software emulation), but it works — and it survives phone reboots.
By the end, you’ll have:
- ✅ A Debian 12 VM running inside your phone
- ✅ Real
dockerd+ Docker Compose v2 - ✅ SSH access from your computer to the VM
- ✅ Auto-start on phone reboot (no manual intervention)
- ✅ A
docker contextsodocker --context phone compose upworks like it’s local
Tested end-to-end on a Samsung Galaxy Note 10+ (SM-N975F, Exynos 9825, 12 GB RAM, Android 12, kernel 4.14.113) — not rooted.
What you’ll need
Requirement Why Cost An Android phone (ARM64, 6+ GB RAM) The host. Must be ARM64 — x86 phones don’t exist, but just in case. Free (you have one) A computer (macOS, Linux, or Windows with WSL) To SSH in from. The phone is the server, your computer is the client. Free Both devices on the same Wi-Fi For SSH. (You can do this remotely later with Tailscale, but that’s out of scope here.) Free A USB-C cable (for the initial ADB setup, optional) One-time setup. You can also do it entirely on-device if you prefer. Free ~2 hours of patience QEMU’s first boot takes 20–30 minutes under software emulation. There’s no way around this. PricelessPhone battery tip: If your phone has a “Protect Battery” or “Maximum 85%” setting (Samsung does), turn it on. The phone will be plugged in 24/7, and capping the charge doubles the battery’s lifespan.
The architecture (mental model)
Here’s what we’re building:
┌────────────────────────────────────────────────────┐
│ Your Android phone (not rooted) │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Termux (regular Android app) │ │
│ │ - QEMU binary (qemu-system-aarch64) │ │
│ │ - The .qcow2 disk image │ │
│ │ - Boot scripts │ │
│ │ - sshd (port 8022) → your computer SSHes │ │
│ │ in here for management │ │
│ └─────────────────┬────────────────────────────┘ │
│ │ launches │
│ ┌─────────────────▼────────────────────────────┐ │
│ │ QEMU VM (looks like real ARM64 hardware) │ │
│ │ - Debian 12 (bookworm) │ │
│ │ - Real dockerd + Docker Compose v2 │ │
│ │ - sshd (port 22 → forwarded to 2222) │ │
│ │ - systemd works (unlike in proot) │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
↑
│ SSH from your computer
│ ssh phone-vm → port 2222 → Debian VM
│ ssh phone-termux → port 8022 → Termux shell
Enter fullscreen mode Exit fullscreen mode
Two SSH targets. phone-vm drops you into Debian where Docker lives. phone-termux drops you into Termux itself (the Android-side layer), useful for troubleshooting the VM or restarting QEMU.
Why QEMU and not just proot-distro? Three reasons:
-
proot-distro has no systemd. No systemd means no
systemctl enable docker. You’d have to manually start the daemon on every login. - proot-distro has no cgroups. No cgroups means no container resource control. Docker will refuse to start.
- proot-distro has no real namespaces. No namespaces means no isolation. Containers would see each other.
QEMU gives us a real Linux kernel with all three. The cost is speed — every instruction is translated by QEMU’s TCG (Tiny Code Generator) software emulator. That’s why boot takes 20–30 minutes.
Step 1: Install Termux (the right way)
Do not install Termux from the Play Store. The Play Store version is frozen at v0.101 from 2020 and has a known security vulnerability. Install from F-Droid or the GitHub releases — but pick one and stick with it, because they’re signature-incompatible with each other.
The cleanest option is F-Droid. Go to f-droid.org/en/packages/com.termux on your phone and install the APK.
While you’re at it, also install Termux:Boot from F-Droid: f-droid.org/en/packages/com.termux.boot. This is what makes QEMU auto-start when the phone reboots. We’ll configure it in Step 7.
After installing Termux:Boot, open it once. Just tap the icon, then close. This registers it with Android’s BOOT_COMPLETED broadcast. If you skip this, the auto-start script in Step 7 won’t fire.
Now open Termux. You should see a $ prompt. Run:
pkg update -y && pkg upgrade -y
Enter fullscreen mode Exit fullscreen mode
This updates the package lists and upgrades everything. First run takes 1–2 minutes.
If it hangs on a mirror, run termux-change-repo, pick “Main repository”, and select a mirror close to you. Indonesian users: linux.domainesia.com and mirror.nevacloud.com are in the Asia group.
Step 2: Set up SSH so you can work from your computer
Typing long commands on a phone keyboard is miserable. Let’s fix that by setting up SSH from your computer.
2.1 Grant storage access (one-time)
In Termux:
termux-setup-storage
Enter fullscreen mode Exit fullscreen mode
A dialog pops up on your phone asking for storage permission. Tap “Allow”. This creates ~/storage/ symlinks to shared storage — not strictly needed for QEMU, but useful later if you want to back up your disk image.
2.2 Set a Termux password
passwd
Enter fullscreen mode Exit fullscreen mode
Type a password twice. You’ll need this for SSH (though we’ll set up key-based auth in a moment).
2.3 Add your computer’s SSH public key
On your computer, get your public key:
# macOS / Linux / WSL
cat ~/.ssh/id_ed25519.pub
Enter fullscreen mode Exit fullscreen mode
If you don’t have one, generate it:
ssh-keygen -t ed25519 -C "[email protected]"
# Press Enter through all the prompts
Enter fullscreen mode Exit fullscreen mode
Copy the output (a string starting with ssh-ed25519 AAAA...).
Back in Termux on the phone:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo 'PASTE_YOUR_PUBLIC_KEY_HERE' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Enter fullscreen mode Exit fullscreen mode
Replace PASTE_YOUR_PUBLIC_KEY_HERE with the key you copied. Keep the single quotes around it.
2.4 Start the SSH server
sshd
Enter fullscreen mode Exit fullscreen mode
No output means it started. Termux’s sshd listens on port 8022 (not the usual 22 — Android reserves 22 for the system).
2.5 Find your phone’s IP
ip addr show wlan0 | grep inet
Enter fullscreen mode Exit fullscreen mode
You’ll see something like inet 192.168.0.9/24. Note the IP (without the /24).
2.6 Set up SSH config on your computer
On your computer, edit ~/.ssh/config and add:
Host phone-termux
HostName 192.168.0.9
Port 8022
User u0_a892
IdentityFile ~/.ssh/id_ed25519
ControlMaster auto
ControlPath ~/.ssh/controlmasters/%r@%h:%p
ControlPersist 10m
Enter fullscreen mode Exit fullscreen mode
Replace 192.168.0.9 with your phone’s IP. The User looks weird (u0_a892) — that’s Termux’s Android UID, which is also its Linux username. The number after u0_a varies per install; to find yours, run whoami in Termux.
Create the controlmasters directory (needed for connection multiplexing, which makes repeated SSH commands nearly instant):
mkdir -p ~/.ssh/controlmasters
Enter fullscreen mode Exit fullscreen mode
Now test:
ssh phone-termux whoami
# → u0_a892
Enter fullscreen mode Exit fullscreen mode
If that works, you’re set. From now on, you can run commands in Termux from the comfort of your computer’s keyboard.
Step 3: Download Debian and build the cloud-init seed
Back in Termux (either on the phone directly, or via ssh phone-termux from your computer):
3.1 Install QEMU and friends
pkg install -y qemu-system-aarch64 openssh curl wget genisoimage
Enter fullscreen mode Exit fullscreen mode
This installs:
-
qemu-system-aarch64— the emulator itself (~400 MB, includes UEFI firmware) -
openssh— for the SSH server we already started -
curl/wget— for downloading the Debian image -
genisoimage— for building the cloud-init seed ISO
3.2 Acquire a wake lock (CRITICAL — do not skip)
termux-wake-lock
Enter fullscreen mode Exit fullscreen mode
This tells Android “don’t kill this process when the screen is off.” Without it, Android’s Doze mode will murder QEMU 5–10 minutes after your screen goes dark, and your VM will die mid-boot. You only need to run this once per Termux session — but it’s easiest to put it in your boot script (Step 7) so it’s always active.
3.3 Download the Debian cloud image
mkdir -p ~/qemu-vm && cd ~/qemu-vm
wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-arm64.qcow2
Enter fullscreen mode Exit fullscreen mode
This is a 500 MB qcow2 file — Debian’s official “generic cloud” image for ARM64. It’s built for exactly this use case (cloud VMs), so it has cloud-init pre-installed and no desktop environment.
3.4 Resize the disk
qemu-img resize debian-12-genericcloud-arm64.qcow2 16G
Enter fullscreen mode Exit fullscreen mode
The downloaded image is ~2 GB (logical size). We resize to 16 GB so there’s room for Docker, containers, and pulled images. The qcow2 format only allocates disk space as it’s used, so this won’t actually consume 16 GB on your phone immediately.
Rename it for clarity:
mv debian-12-genericcloud-arm64.qcow2 debian-12-arm64.qcow2
Enter fullscreen mode Exit fullscreen mode
3.5 Create the cloud-init seed
Cloud-init is how we configure the VM on first boot: set hostname, create users, add SSH keys. It reads from a “seed” — in our case, a small ISO file attached as a virtual CD-ROM.
Create user-data:
cat > user-data <<'EOF'
#cloud-config
hostname: docker-phone
users:
- name: sulthon
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAA...YOUR_KEY_HERE... [email protected]
ssh_pwauth: false
disable_root: false
package_update: true
packages:
- qemu-guest-agent
- ca-certificates
- curl
growpart:
mode: auto
devices: ['/']
ignore_growroot: false
EOF
Enter fullscreen mode Exit fullscreen mode
Edit the SSH key to match your actual ~/.ssh/id_ed25519.pub from Step 2.3. This is what lets you SSH into the Debian VM later. Change the username sulthon to whatever you like — just remember it for later steps.
Create meta-data:
cat > meta-data <<'EOF'
instance-id: docker-phone-001
local-hostname: docker-phone
EOF
Enter fullscreen mode Exit fullscreen mode
Build the seed ISO:
genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data
Enter fullscreen mode Exit fullscreen mode
The -volid cidata is critical — cloud-init looks for a volume labeled exactly cidata (or CIDATA). If you get this wrong, cloud-init won’t run and you’ll have no users, no SSH keys, no nothing.
Step 4: The QEMU launcher script
This is the script that boots Debian. There’s a lot going on, so let’s break it down carefully.
Copy the UEFI firmware variables first (one-time):
cp $PREFIX/share/qemu/edk2-aarch64-vars.fd ~/qemu-vm/edk2-vars.fd
truncate -s 64M ~/qemu-vm/edk2-vars.fd
Enter fullscreen mode Exit fullscreen mode
This creates a writable copy of the UEFI NVRAM (the edk2-vars.fd file). The truncate grows it to 64 MB for safety margin. Don’t skip this — without the vars file, UEFI state won’t persist across VM reboots, and your VM won’t boot a second time.
Now create the launcher script. This is the heart of the whole setup:
cat > ~/boot-debian-mon.sh <<'EOF'
#!/data/data/com.termux/files/usr/bin/bash
set +e
VM_DIR="/data/data/com.termux/files/home/qemu-vm"
CODE="/data/data/com.termux/files/usr/share/qemu/edk2-aarch64-code.fd"
VARS="$VM_DIR/edk2-vars.fd"
IMG="$VM_DIR/debian-12-arm64.qcow2"
SEED="$VM_DIR/seed.iso"
MONSOCK="$VM_DIR/mon.sock"
SERIALSOCK="$VM_DIR/serial.sock"
LOG="$VM_DIR/debian-boot.log"
pkill -9 -f qemu-system-aarch64 2>/dev/null
sleep 2
rm -f $MONSOCK $SERIALSOCK
setsid qemu-system-aarch64
-name docker-phone
-machine virt,gic-version=3
-cpu max,aarch64=on,pmu=on
-smp 6
-m 6144
-accel tcg,thread=multi,tb-size=512
-nodefaults
-chardev socket,id=mon0,path=$MONSOCK,server=on,wait=off
-mon chardev=mon0,mode=readline
-chardev socket,id=ser0,path=$SERIALSOCK,server=on,wait=off,logfile=$LOG
-serial chardev:ser0
-display none
-drive if=pflash,format=raw,readonly=on,file=$CODE
-drive if=pflash,format=raw,file=$VARS
-drive file=$IMG,if=virtio,format=qcow2,cache=writeback
-drive file=$SEED,if=virtio,format=raw,readonly=on
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80,hostfwd=tcp::9000-:9000
-device virtio-net-pci,netdev=net0
-device virtio-rng-pci
-rtc base=utc
> $LOG 2>&1 &
disown
sleep 3
echo "QEMU PID: $(pgrep -f qemu-system-aarch64 | head -1)"
echo "Monitor socket: $MONSOCK"
echo "Serial socket: $SERIALSOCK"
echo "Log: $LOG"
EOF
chmod +x ~/boot-debian-mon.sh
Enter fullscreen mode Exit fullscreen mode
What each line does
The machine definition:
-
-machine virt,gic-version=3— QEMU’s “virt” machine, which is designed for VMs (not emulating any specific real phone). GIC version 3 is the modern ARM interrupt controller. -
-cpu max,aarch64=on,pmu=on— expose the maximum CPU feature set to the guest, including hardware crypto (ARMv8 crypto extensions). This makes TLS handshakes faster. -
-smp 6— give the VM 6 CPUs. Adjust based on your phone (8-core phones typically have a “big.LITTLE” layout; 6 leaves 2 for Android). -
-m 6144— give the VM 6 GB of RAM. Leave the rest for Android.
The emulator:
-
-accel tcg,thread=multi,tb-size=512— use TCG (software emulation) with multi-threading and a 512 MB translation block cache. This is the best TCG config for sustained throughput.
Storage:
- The first
-drive if=pflash,readonly=onis the UEFI firmware code (read-only). - The second
-drive if=pflashis the UEFI vars file (writable — this is why we made the copy earlier). - The
-drive file=$IMG,if=virtiois your Debian disk. - The
-drive file=$SEED,if=virtio,readonly=onis the cloud-init seed ISO.
Networking:
-
-netdev user,id=net0,hostfwd=tcp::2222-:22,...— user-mode networking with port forwarding. Anything connecting to port 2222 on the phone gets forwarded to port 22 inside the VM. We also forward 8080 and 9000 for web apps you might run later.
Process management (the tricky part):
-
setsid— detaches QEMU from Termux’s session leader. Without this, when SSH disconnects, Android kills the whole process tree. -
disown— removes QEMU from the shell’s job table. - Together, these mean QEMU survives SSH disconnects. Do not use plain
nohup— it’s not enough on Termux.
Step 5: First boot (patience required)
Launch the VM:
bash ~/boot-debian-mon.sh
Enter fullscreen mode Exit fullscreen mode
You should see:
QEMU PID: 12345
Monitor socket: /data/data/com.termux/files/home/qemu-vm/mon.sock
Serial socket: /data/data/com.termux/files/home/qemu-vm/serial.sock
Log: /data/data/com.termux/files/home/qemu-vm/debian-boot.log
Enter fullscreen mode Exit fullscreen mode
Now wait. 20–30 minutes. No, that’s not a typo. TCG software emulation is brutally slow.
You can watch the boot log from another SSH session:
# From your computer:
ssh phone-termux tail -f ~/qemu-vm/debian-boot.log
Enter fullscreen mode Exit fullscreen mode
When you see something like:
[ OK ] Started OpenSSH server
Enter fullscreen mode Exit fullscreen mode
…and the log stops growing, the VM is ready. Verify from your computer:
ssh -p 2222 [email protected] hostname
# → docker-phone
Enter fullscreen mode Exit fullscreen mode
(Reminder: change sulthon to whatever username you put in user-data.)
If that works, add a second SSH config entry on your computer for the VM:
# append to ~/.ssh/config
Host phone-vm
HostName 192.168.0.9
Port 2222
User sulthon
IdentityFile ~/.ssh/id_ed25519
ControlMaster auto
ControlPath ~/.ssh/controlmasters/%r@%h:%p
ControlPersist 10m
Enter fullscreen mode Exit fullscreen mode
Now ssh phone-vm Just Works.
Step 6: Install Docker inside the VM
SSH into the VM and install everything:
ssh phone-vm
Enter fullscreen mode Exit fullscreen mode
6.1 Install Docker
sudo apt-get update
sudo apt-get install -y docker.io
Enter fullscreen mode Exit fullscreen mode
The docker.io package is Debian’s official Docker package (version 20.10.24 as of this writing). It’s slightly older than Docker CE, but it’s in the main Debian repos and works perfectly. Expect this to take 25–35 minutes under TCG. Go get coffee.
6.2 Install Docker Compose v2
sudo mkdir -p /usr/libexec/docker/cli-plugins
sudo curl -fSL -o /usr/libexec/docker/cli-plugins/docker-compose
https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-aarch64
sudo chmod +x /usr/libexec/docker/cli-plugins/docker-compose
Enter fullscreen mode Exit fullscreen mode
We download the binary directly because the docker-compose-v2 Debian package isn’t in bookworm. Placing it in cli-plugins/ means docker compose (with a space, not a hyphen) works.
6.3 Let your user run Docker without sudo
sudo usermod -aG docker $USER
Enter fullscreen mode Exit fullscreen mode
You’ll need to log out and back in for this to take effect:
exit # back to your computer
ssh phone-vm # back in
Enter fullscreen mode Exit fullscreen mode
6.4 Configure Docker for the slow VM
This step is critical. Without it, docker pull will fail with TLS handshake timeout because TCG is too slow for Docker’s default timeouts.
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"max-concurrent-downloads": 1,
"max-download-attempts": 5,
"dns": ["8.8.8.8", "1.1.1.1"],
"ip6tables": false,
"ipv6": false
}
EOF
sudo systemctl restart docker
Enter fullscreen mode Exit fullscreen mode
What each setting does:
-
max-concurrent-downloads: 1— only download one layer at a time. Parallel downloads overwhelm TCG and time out. -
max-download-attempts: 5— retry failed layers. -
dns: [8.8.8.8, 1.1.1.1]— Docker’s embedded DNS sometimes can’t resolve registry hostnames under TCG; this forces public DNS. -
ip6tables: falseandipv6: false— disable IPv6. TCG’s IPv6 stack is even slower than IPv4, and many container images misbehave over IPv6 anyway.
6.5 Add ZRAM swap (recommended)
ZRAM is compressed swap in RAM. Under TCG, real disk I/O is brutal, so having compressed memory swap gives you a safety net for memory spikes.
sudo apt-get install -y zram-tools
echo -e "ALGO=zstdnPERCENT=75nPRIORITY=100" | sudo tee /etc/default/zramswap
sudo systemctl restart zramswap
Enter fullscreen mode Exit fullscreen mode
This gives you ~4.3 GB of zstd-compressed swap (assuming 6 GB of VM RAM). zstd compresses roughly 3:1 on typical workload data, so it’s like having ~13 GB of effective memory.
6.6 Verify
sudo docker run --rm hello-world
Enter fullscreen mode Exit fullscreen mode
Expect:
Unable to find image 'hello-world:latest' locally
... pulling layers ...
Hello from Docker!
This message shows that your installation appears to be working correctly.
Enter fullscreen mode Exit fullscreen mode
The pull takes ~75 seconds (5 KB image, but TCG). The run takes ~15 seconds after that. If you see “Hello from Docker!”, you’re done with the hard part.
Step 7: Make it survive phone reboots
If the phone reboots (battery died, OS update, accidental power button press), you want QEMU to come back automatically. Termux:Boot handles this.
7.1 Create the Termux:Boot scripts
Back in Termux (via ssh phone-termux):
mkdir -p ~/.termux/boot
Enter fullscreen mode Exit fullscreen mode
Create the first script — auto-start QEMU:
cat > ~/.termux/boot/01-start-vm.sh <<'EOF'
#!/data/data/com.termux/files/usr/bin/bash
sleep 15
termux-wake-lock 2>/dev/null
pkill -9 -f qemu-system-aarch64 2>/dev/null
sleep 2
bash ~/boot-debian-mon.sh
EOF
chmod +x ~/.termux/boot/01-start-vm.sh
Enter fullscreen mode Exit fullscreen mode
The sleep 15 gives Android time to finish booting before we start hammering the CPU. The pkill is a safety net in case QEMU somehow came back half-alive.
Create the second script — auto-start Termux sshd:
cat > ~/.termux/boot/02-start-sshd.sh <<'EOF'
#!/data/data/com.termux/files/usr/bin/bash
sleep 5
sshd
EOF
chmod +x ~/.termux/boot/02-start-sshd.sh
Enter fullscreen mode Exit fullscreen mode
7.2 Verify the scripts are registered
If you’ve opened Termux:Boot once (Step 1), Termux automatically picks up scripts in ~/.termux/boot/ and runs them at boot. To confirm:
ls -la ~/.termux/boot/
# Should show:
# -rwx------ 1 u0_a892 u0_a892 ... 01-start-vm.sh
# -rwx------ 1 u0_a892 u0_a892 ... 02-start-sshd.sh
Enter fullscreen mode Exit fullscreen mode
7.3 Test it (optional but recommended)
Reboot your phone the normal Android way. After it restarts:
- Wait ~5 minutes for Android to fully boot.
- Wait another ~20 minutes for QEMU to cold-boot Debian.
- From your computer:
ssh phone-vm hostnameshould returndocker-phone.
If that works, your phone is a self-healing Docker host. You can unplug it, plug it back in, reboot it, whatever — it’ll come back.
Step 8: Use it from your computer like it’s local
Typing ssh phone-vm docker run ... for everything gets old. Let’s make the phone feel like a local Docker host.
8.1 Create a Docker context
On your computer:
docker context create phone --docker "host=ssh://[email protected]:2222"
Enter fullscreen mode Exit fullscreen mode
(Change sulthon to your VM username.)
Docker’s SSH integration bypasses your ~/.ssh/config aliases, so you need to add the phone to ~/.ssh/known_hosts explicitly:
ssh-keyscan -p 2222 -t ed25519 192.168.0.9 >> ~/.ssh/known_hosts
Enter fullscreen mode Exit fullscreen mode
Verify:
docker --context phone ps
docker --context phone images
docker --context phone run --rm hello-world
Enter fullscreen mode Exit fullscreen mode
8.2 Use Docker Compose against the phone
Anywhere you have a docker-compose.yml:
docker --context phone compose up -d
docker --context phone compose logs -f
docker --context phone compose down
Enter fullscreen mode Exit fullscreen mode
This is the magic. The phone is now indistinguishable from a remote Docker host.
8.3 (Optional) Make it the default
docker context use phone
Enter fullscreen mode Exit fullscreen mode
Now docker ps (without --context) hits the phone. Be careful with this — it’s surprising when you forget and accidentally push an image to the phone over Wi-Fi instead of your local Docker. I prefer to leave it as --context phone explicitly.
Troubleshooting: the 5 things most likely to break
1. “QEMU died after my SSH disconnected”
You forgot setsid and disown in the launcher script, or you launched it manually with nohup (which is not enough). Use the script from Step 4 as-is. Never launch QEMU directly with qemu-system-aarch64 ... — always go through the script.
2. “Docker pull fails with TLS handshake timeout”
You skipped the /etc/docker/daemon.json config in Step 6.4, or the config is malformed. Verify:
ssh phone-vm cat /etc/docker/daemon.json
ssh phone-vm sudo systemctl restart docker
Enter fullscreen mode Exit fullscreen mode
Then retry. If it still fails, check that max-concurrent-downloads is set to 1 and not to a higher number.
3. “Termux sshd isn’t running after reboot”
Either you forgot to open Termux:Boot once (Step 1), or the ~/.termux/boot/02-start-sshd.sh script isn’t executable. Fix:
ssh phone-termux chmod +x ~/.termux/boot/02-start-sshd.sh
Enter fullscreen mode Exit fullscreen mode
And open the Termux:Boot app icon on your phone, just to be safe.
4. “The VM boots to a UEFI shell instead of Debian”
This is a known issue with Debian cloud images on some QEMU versions. The fix is to write a startup.nsh script to the EFI System Partition. This is fiddly — you need to boot Alpine from ISO with your qcow2 as a data disk, mount the ESP, and write the file. I’ll cover this in detail in a follow-up post. For now, if this happens, leave a comment on this post and I’ll walk you through it.
5. “Everything worked yesterday, but today it’s broken”
You hit Android’s phantom process killer. Modern Android (12+) kills background processes that use too much CPU. The fixes:
- Keep the phone plugged in. Battery-saver mode is brutal.
-
Keep
termux-wake-lockactive. Runssh phone-termux termux-wake-lockto verify. - Disable Samsung Game Tuning / GOS if you’re on a Samsung device. It throttles sustained CPU workloads. Settings → Gaming Services → Game Booster → Maximum Performance (or similar).
- Check Settings → Battery → Termux → Unrestricted. This should be automatic via Termux:Boot, but if it’s not, set it manually.
What to do next
You now have a working Docker host that costs $0/month and fits in your pocket. Some ideas:
- Run a personal Traefik + whoami demo to verify compose works end-to-end. Port 8080 is already forwarded.
- Self-host Postgres for local dev. Use a volume so data survives container restarts.
- Run Watchtower to keep your containers auto-updated.
- Add Tailscale to the VM so you can reach your Docker host from anywhere — not just your Wi-Fi.
-
Build images on the phone.
docker buildworks fine (slowly) under TCG. Useful for iterating without hitting your laptop’s battery.
The phone-as-server dream is real. It just takes QEMU to get there. Enjoy your free Docker host.
If you find bugs in this tutorial, please leave a comment — I’ll keep it updated. Happy hacking.
답글 남기기