Date: Tuesday, April 15, 2025 at 11:20 PM KST
This guide provides instructions for setting up and using Docker within Termux on an ARM64 device like the Meta Quest 2:
- Part 1: Running the Docker Daemon inside a lightweight x86_64 Alpine Linux Virtual Machine using QEMU, installed via ISO with an automated script.
- Part 2: Installing the Docker Client inside your existing ARM64 Ubuntu 24.04
proot
environment and configuring it to control the Docker Daemon running in the QEMU VM.
Important Considerations:
- Performance: QEMU system emulation is computationally expensive and will be significantly slower than native execution. Docker performance will be impacted.
- Storage: QEMU virtual machine images require substantial storage space.
- RAM: QEMU VMs require dedicated RAM allocation.
- Complexity: This setup involves multiple layers (Termux -> QEMU -> Alpine -> Docker).
- Networking: We will configure the Docker daemon to listen on a TCP port without TLS for simplicity within the QEMU user network. This is insecure if exposed externally. Do not expose the Docker TCP port directly to untrusted networks.
- Termux installed on the Quest 2 (F-Droid/GitHub version strongly recommended).
- Sufficient free storage space (recommend 10GB+ for Part 1).
- An existing
proot-distro
Ubuntu 24.04 ARM64 environment set up (for Part 2). - Patience!
This method uses a script to automate the creation and installation of an x86_64 Alpine Linux VM, which will host the Docker daemon.
Step 1.1: Install Prerequisites & Create Directory
- Open Termux and install QEMU and utilities:
# (Termux) pkg update && pkg upgrade -y pkg install qemu-system-x86_64 qemu-utils wget mtools openssl-tool coreutils
- Create a dedicated directory and enter it:
# (Termux) mkdir ~/alpine cd ~/alpine
Step 1.2: Create the Enhanced QEMU Launch Script
- Create the main script file
start_docker_vm.sh
:# (Termux - inside ~/alpine) nano start_docker_vm.sh
- Paste the entire following script content into
nano
. Note the added port forwards for Docker API (23750 -> 2375) and an example VS Code server (8443 -> 8443).#!/data/data/com.termux/files/usr/bin/bash # Exit immediately if a command exits with a non-zero status. set -e # --- Configuration --- MEM_MB=1536 # Memory in MB CPUS=$(nproc) # Use all available cores, or set manually e.g., CPUS=2 DISK_IMG="alpine_disk.qcow2" # Virtual disk filename DISK_SIZE="10G" # Virtual disk size (Increased slightly for Docker images) ALPINE_VERSION="3.21.3" # Target Alpine version ALPINE_ARCH="x86_64" ALPINE_FLAVOR="extended" # Use "extended" for more tools ISO_FILE="alpine-${ALPINE_FLAVOR}-${ALPINE_VERSION}-${ALPINE_ARCH}.iso" ISO_URL="[https://dl-cdn.alpinelinux.org/alpine/v$](https://dl-cdn.alpinelinux.org/alpine/v$){ALPINE_VERSION%.*}/releases/${ALPINE_ARCH}/${ISO_FILE}" FLOPPY_IMG="floppy.img" # Floppy image filename ANSWER_FILE="answerfile" # Answerfile filename # Host ports for forwarding SSH_HOST_PORT=2222 # Host port -> VM SSH port 22 DOCKER_API_HOST_PORT=23750 # Host port -> VM Docker API port 2375 DOCKER_HOST_PORT=9090 # Example: Host port -> VM Portainer/Service port 9000 VSCODE_HOST_PORT=8443 # Example: Host port -> VM VSCode Server port 8443 # Add more hostfwd rules as needed: ,hostfwd=tcp::<host_port>-:<guest_port> HOSTFWD_RULES="hostfwd=tcp::${SSH_HOST_PORT}-:22,hostfwd=tcp::${DOCKER_API_HOST_PORT}-:2375,hostfwd=tcp::${DOCKER_HOST_PORT}-:9000,hostfwd=tcp::${VSCODE_HOST_PORT}-:8443" # --- Script Logic --- INIT_MODE=false if [[ "$1" == "--init" ]]; then INIT_MODE=true echo "--- Running in INIT mode: Will install Alpine OS ---" else echo "--- Running in normal mode: Booting existing Alpine OS ---" fi cd "$(dirname "$0")" # Ensure running in script's directory # Download Alpine ISO if it doesn't exist if [ ! -f "$ISO_FILE" ]; then echo ">>> Downloading Alpine ISO ($ISO_FILE)..." wget --no-check-certificate -O "$ISO_FILE" "$ISO_URL" # Added --no-check-certificate just in case if [ $? -ne 0 ]; then echo "Error downloading ISO. Exiting."; exit 1; fi echo ">>> ISO Download complete." else echo ">>> Alpine ISO ($ISO_FILE) already exists." fi # Create Virtual Disk if it doesn't exist if [ ! -f "$DISK_IMG" ]; then echo ">>> Creating virtual disk ($DISK_IMG, Size: $DISK_SIZE)..." qemu-img create -f qcow2 "$DISK_IMG" "$DISK_SIZE" if [ $? -ne 0 ]; then echo "Error creating disk image. Exiting."; exit 1; fi echo ">>> Virtual disk created." else echo ">>> Virtual disk ($DISK_IMG) already exists." if [ "$INIT_MODE" = true ]; then echo ">>> WARNING: Running --init with existing disk. Installation might fail or overwrite." fi fi # --- Create Answerfile and Floppy only in INIT mode --- if [ "$INIT_MODE" = true ]; then echo ">>> Preparing answerfile for automated installation..." ROOT_PASSWORD="" while [ -z "$ROOT_PASSWORD" ]; do read -sp "Enter desired root password for Alpine VM: " ROOT_PASSWORD echo if [ -z "$ROOT_PASSWORD" ]; then echo "Password cannot be empty."; fi done echo ">>> Generating password hash..." ROOT_HASH=$(printf "%s" "$ROOT_PASSWORD" | openssl passwd -6 -stdin) if [ $? -ne 0 ] || [ -z "$ROOT_HASH" ]; then echo "Error generating hash."; unset ROOT_PASSWORD; exit 1; fi unset ROOT_PASSWORD echo ">>> Creating answerfile ($ANSWER_FILE)..." cat << EOF > "$ANSWER_FILE" # Alpine Linux setup answer file KEYMAPOPTS="us us" HOSTNAMEOPTS="-n docker-vm" INTERFACESOPTS="auto lo\niface lo inet loopback\n\nauto eth0\niface eth0 inet dhcp\n hostname docker-vm" DNSOPTS="-d localdomain 8.8.8.8 1.1.1.1" TIMEZONEOPTS="-z UTC" # Adjust timezone if needed PROXYOPTS="none" APKREPOSOPTS="-r f" SSHDAEMONOPTS="-c openssh" NTPOPTS="-c chrony" DISKOPTS="-m sys /dev/vda" ROOT_PASSWD=${ROOT_HASH} EOF if [ $? -ne 0 ]; then echo "Error creating answerfile."; exit 1; fi echo ">>> Creating floppy image ($FLOPPY_IMG)..." dd if=/dev/zero of="$FLOPPY_IMG" bs=1k count=1440 status=none if [ $? -ne 0 ]; then echo "Error creating floppy image."; exit 1; fi echo ">>> Copying answerfile to floppy..." mcopy -i "$FLOPPY_IMG" "$ANSWER_FILE" ::/ if [ $? -ne 0 ]; then echo "Error copying answerfile."; exit 1; fi rm "$ANSWER_FILE" # Clean up echo ">>> Answerfile preparation complete." fi # End of INIT_MODE specific block # --- Construct QEMU Command --- QEMU_ARGS=( -machine q35 -m ${MEM_MB} -smp cpus=${CPUS} -cpu qemu64 -accel tcg -drive if=pflash,format=raw,read-only=on,file=$PREFIX/share/qemu/edk2-x86_64-code.fd -netdev user,id=n1,"${HOSTFWD_RULES}" # Use variable for hostfwd rules -device virtio-net-pci,netdev=n1 -drive file=${DISK_IMG},if=virtio,format=qcow2 -nographic ) if [ "$INIT_MODE" = true ]; then echo ">>> Adding ISO boot options for installation..." QEMU_ARGS+=(-drive if=floppy,format=raw,file=${FLOPPY_IMG}) QEMU_ARGS+=(-boot d) QEMU_ARGS+=(-cdrom ${ISO_FILE}) fi # --- Launch QEMU --- echo ">>> Starting QEMU..." qemu-system-x86_64 "${QEMU_ARGS[@]}" echo "QEMU VM exited."
- Save the script (Ctrl+X, Y, Enter).
- Make it executable:
# (Termux - inside ~/alpine) chmod +x start_docker_vm.sh
Step 1.3: First Boot & Automated Installation
- Run the script with the
--init
flag:# (Termux - inside ~/alpine) ./start_docker_vm.sh --init
- Enter the desired root password when prompted.
- Alpine Linux will install automatically using the answerfile. Monitor the console.
- When installation finishes, shut down the VM from the Alpine console:
# (Inside Alpine VM Console) poweroff
- Wait for QEMU to exit.
Step 1.4: Subsequent Boots & Docker Daemon Setup
- Run the script without the
--init
flag:# (Termux - inside ~/alpine) ./start_docker_vm.sh
- Log into your Alpine VM using the root password (via console or SSH on port 2222):
# (Termux or another terminal) ssh root@localhost -p 2222
- Enable Community Repository: Edit
/etc/apk/repositories
and uncomment the/community
line. - Install Docker:
# (Inside Alpine VM) apk update apk add docker docker-cli-compose
- Configure Docker Daemon for TCP Access:
- Create/edit the daemon configuration file:
# (Inside Alpine VM) mkdir -p /etc/docker nano /etc/docker/daemon.json
- Add the following content to listen on both the Unix socket and TCP port 2375 (adjust if needed):
{ "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"] }
- WARNING: Exposing the Docker daemon via TCP without TLS is insecure. Only do this if you understand the risks and trust the network environment (like the internal QEMU user network).
- Save the file (Ctrl+X, Y, Enter in nano).
- Create/edit the daemon configuration file:
- Start and Enable Docker Service:
# (Inside Alpine VM) rc-service docker start rc-update add docker boot
The QEMU VM is now running with the Docker daemon accessible over TCP port 2375 within the VM's network. The start_docker_vm.sh
script forwards host port 23750
to this internal port.
This part involves installing the Docker client in your ARM64 Ubuntu proot
environment and configuring it to connect to the Docker daemon running inside the QEMU VM (from Part 1).
Step 2.1: Add Docker's Official APT Repository (Inside Ubuntu proot)
- Log into your Ubuntu 24.04 ARM64 environment:
# (Termux) proot-distro login ubuntu # Or ubuntu-24.04
- Install prerequisite packages:
# (Ubuntu ARM64) sudo apt update sudo apt install -y ca-certificates curl gpg
- Create the keyring directory:
# (Ubuntu ARM64) sudo install -m 0755 -d /etc/apt/keyrings
- Download Docker's official GPG key:
# (Ubuntu ARM64) sudo curl -fsSL [https://download.docker.com/linux/ubuntu/gpg](https://download.docker.com/linux/ubuntu/gpg) -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc
- Add the Docker repository to Apt sources:
# (Ubuntu ARM64) echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] [https://download.docker.com/linux/ubuntu](https://download.docker.com/linux/ubuntu) \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Step 2.2: Install Docker Client Packages (Inside Ubuntu proot)
- Update the package list to include the Docker repository:
# (Ubuntu ARM64) sudo apt update
- Install the Docker CLI, containerd (needed by CLI), and Compose plugin:
(We only need the client tools, not the full
# (Ubuntu ARM64) sudo apt install -y docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker-ce
engine package)
Step 2.3: Configure Docker Host Environment Variable (Inside Ubuntu proot)
- Tell the Docker client to connect to the daemon running in QEMU via the forwarded TCP port (
23750
on the Termux host, as defined instart_docker_vm.sh
). Set theDOCKER_HOST
environment variable. For persistence, add it to your user's shell profile:# (Ubuntu ARM64 - Run as the user who will use docker, e.g., ikko or root) echo "export DOCKER_HOST=tcp://localhost:23750" >> ~/.bashrc # Or for system-wide for new logins: sudo nano /etc/profile.d/docker_host.sh
- Apply the variable to your current session (or log out and back in):
# (Ubuntu ARM64) export DOCKER_HOST=tcp://localhost:23750
Step 2.4: Test Connection
- From within the Ubuntu
proot
environment, run:# (Ubuntu ARM64) docker version
- The
Client:
section should show your ARM64 client details. - The
Server:
section should show details from the Docker daemon running in the QEMU VM (Architecture:x86_64
).
- The
- You can also run:
# (Ubuntu ARM64) docker info
Step 2.5: Example: Running Python 3.12 amd64 Container
Now you can use the docker
command in Ubuntu proot
to run amd64
containers via the QEMU daemon:
# (Ubuntu ARM64)
docker run --rm -it python:3.12-slim python --version