Skip to content

Instantly share code, notes, and snippets.

@1kko
Last active April 15, 2025 15:03
Show Gist options
  • Save 1kko/e6de0a6cd0dc35ddb4ee85e947726ed8 to your computer and use it in GitHub Desktop.
Save 1kko/e6de0a6cd0dc35ddb4ee85e947726ed8 to your computer and use it in GitHub Desktop.
Docker via QEMU & Controlling it from Ubuntu proot

Guide: Docker via QEMU & Controlling it from Ubuntu proot

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:

  1. Part 1: Running the Docker Daemon inside a lightweight x86_64 Alpine Linux Virtual Machine using QEMU, installed via ISO with an automated script.
  2. 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.

Prerequisites

  • 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!

Part 1: Setting up the Docker Daemon in QEMU (x86_64 VM)

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

  1. 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
  2. Create a dedicated directory and enter it:
    # (Termux)
    mkdir ~/alpine
    cd ~/alpine

Step 1.2: Create the Enhanced QEMU Launch Script

  1. Create the main script file start_docker_vm.sh:
    # (Termux - inside ~/alpine)
    nano start_docker_vm.sh
  2. 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."
  3. Save the script (Ctrl+X, Y, Enter).
  4. Make it executable:
    # (Termux - inside ~/alpine)
    chmod +x start_docker_vm.sh

Step 1.3: First Boot & Automated Installation

  1. Run the script with the --init flag:
    # (Termux - inside ~/alpine)
    ./start_docker_vm.sh --init
  2. Enter the desired root password when prompted.
  3. Alpine Linux will install automatically using the answerfile. Monitor the console.
  4. When installation finishes, shut down the VM from the Alpine console:
    # (Inside Alpine VM Console)
    poweroff
  5. Wait for QEMU to exit.

Step 1.4: Subsequent Boots & Docker Daemon Setup

  1. Run the script without the --init flag:
    # (Termux - inside ~/alpine)
    ./start_docker_vm.sh
  2. 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
  3. Enable Community Repository: Edit /etc/apk/repositories and uncomment the /community line.
  4. Install Docker:
    # (Inside Alpine VM)
    apk update
    apk add docker docker-cli-compose
  5. 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).
  6. 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.

Part 2: Controlling the QEMU Docker Daemon from Ubuntu (proot)

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)

  1. Log into your Ubuntu 24.04 ARM64 environment:
    # (Termux)
    proot-distro login ubuntu # Or ubuntu-24.04
  2. Install prerequisite packages:
    # (Ubuntu ARM64)
    sudo apt update
    sudo apt install -y ca-certificates curl gpg
  3. Create the keyring directory:
    # (Ubuntu ARM64)
    sudo install -m 0755 -d /etc/apt/keyrings
  4. 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
  5. 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)

  1. Update the package list to include the Docker repository:
    # (Ubuntu ARM64)
    sudo apt update
  2. Install the Docker CLI, containerd (needed by CLI), and Compose plugin:
    # (Ubuntu ARM64)
    sudo apt install -y docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    (We only need the client tools, not the full docker-ce engine package)

Step 2.3: Configure Docker Host Environment Variable (Inside Ubuntu proot)

  1. Tell the Docker client to connect to the daemon running in QEMU via the forwarded TCP port (23750 on the Termux host, as defined in start_docker_vm.sh). Set the DOCKER_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
  2. 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

  1. 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).
  2. 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment