Skip to content

Instantly share code, notes, and snippets.

@oofnikj
Last active October 27, 2025 01:48
Show Gist options
  • Save oofnikj/e79aef095cd08756f7f26ed244355d62 to your computer and use it in GitHub Desktop.
Save oofnikj/e79aef095cd08756f7f26ed244355d62 to your computer and use it in GitHub Desktop.
Install Docker on Termux
KEYMAPOPTS="us us"
HOSTNAMEOPTS="-n alpine"
INTERFACESOPTS="auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
hostname alpine
"
TIMEZONEOPTS="-z UTC"
PROXYOPTS="none"
APKREPOSOPTS="http://dl-cdn.alpinelinux.org/alpine/v3.12/main http://dl-cdn.alpinelinux.org/alpine/v3.12/community"
SSHDOPTS="-c openssh"
NTPOPTS="-c busybox"
DISKOPTS="-v -m sys -s 0 /dev/sda"

Docker on Termux [in a VM]

Create a Linux VM and install Docker in it so you can (slowly) run x86 Docker containers on your Android device.

Recommended to use SSH or external keyboard to execute the following commands unless you want sore thumbs. See https://wiki.termux.com/wiki/Remote_Access#SSH

  • Install QEMU

     pkg install qemu-utils qemu-common qemu-system-x86_64-headless
    
  • Download Alpine Linux 3.12 (virt optimized) ISO

     mkdir alpine && cd $_
     wget http://dl-cdn.alpinelinux.org/alpine/v3.12/releases/x86_64/alpine-virt-3.12.3-x86_64.iso
    
  • Create disk (note it won't actually take 4GB of space, more like 500MB)

     qemu-img create -f qcow2 alpine.img 4G
    
  • Boot it up

    qemu-system-x86_64 -machine q35 -m 1024 -smp cpus=2 -cpu qemu64 \
      -drive if=pflash,format=raw,read-only,file=$PREFIX/share/qemu/edk2-x86_64-code.fd \
      -netdev user,id=n1,hostfwd=tcp::2222-:22 -device virtio-net,netdev=n1 \
      -cdrom alpine-virt-3.12.3-x86_64.iso \
      -nographic alpine.img
    
  • Login with user root (no password)

  • Setup network (press Enter to use defaults):

     localhost:~# setup-interfaces
     Available interfaces are: eth0.
     Enter '?' for help on bridges, bonding and vlans.
     Which one do you want to initialize? (or '?' or 'done') [eth0] 
     Ip address for eth0? (or 'dhcp', 'none', '?') [dhcp] 
     Do you want to do any manual network configuration? [no] 
     localhost:~# ifup eth0
    
  • Create an answerfile to speed up installation:

    localhost:~# wget https://gist.github.com/oofnikj/e79aef095cd08756f7f26ed244355d62/raw/answerfile
    
  • Patch setup-disk to enable serial console output on boot

    localhost:~# sed -i -E 's/(local kernel_opts)=.*/\1="console=ttyS0"/' /sbin/setup-disk
    
  • Run setup to install to disk

    localhost:~# setup-alpine -f answerfile
    
  • Once installation is complete, power off the VM (command poweroff) and boot again without cdrom:

    qemu-system-x86_64 -machine q35 -m 1024 -smp cpus=2 -cpu qemu64 \
      -drive if=pflash,format=raw,read-only,file=$PREFIX/share/qemu/edk2-x86_64-code.fd \
      -netdev user,id=n1,hostfwd=tcp::2222-:22 -device virtio-net,netdev=n1 \
      -nographic alpine.img
    
  • Install docker and enable on boot:

    alpine:~# apk update && apk add docker
    alpine:~# service docker start
    alpine:~# rc-update add docker
    
  • Useful keys:

    • Ctrl+a x: quit emulation
    • Ctrl+a h: toggle QEMU console
@kqvanity
Copy link

@egandro most container don't support arm architectures

@orangeed
Copy link

orangeed commented Sep 19, 2025 via email

@geomlattice
Copy link

geomlattice commented Sep 20, 2025

There is also udocker (as native - but limited - solution on termux)

$ pkg install udocker
$ udocker run hello-world

@egandro Do you know how to properly publish ports? I have tried the following to no avail

udocker run --publish=3333:8888 $CONTAINER_ID

@egandro
Copy link

egandro commented Sep 20, 2025

@egandro most container don't support arm architectures

I agree for the old armv7. A lot support arm64.

@egandro
Copy link

egandro commented Sep 20, 2025

@geomlattice Yes this is a bit odd...

udocker run --publish=3333:8888 $CONTAINER_ID

There is no port forwarding as we have in docker ... Exposed ports > 1024 are directly used and any other are changed.

mkdir -p $HOME/nginx-logs
udocker run \
  --volume=$HOME/nginx-logs:/var/log/nginx \
  nginx

Error: this container exposes privileged TCP/IP ports

 ******************************************************************************
 *                                                                            *
 *               STARTING XXXXXXXXXXXXXXXXXXXXXXXX                *
 *                                                                            *
 ******************************************************************************
 executing: docker-entrypoint.sh
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: ipv6 not available
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up

ATTENTION: A bind system call was requested on port: 80
The port has been changed. If connecting from outside Termux, use: 2080

...

$ curl localhost:2080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

I have no idea if there is a (firewall type) port forwarding as we have in regular docker. But there is an easy work around with custom Dockerfiles.

@valorisa
Copy link

To work around this behavior with udocker, it is recommended to create a custom Dockerfile that exposes a port higher than 1024, or to configure nginx to listen natively on a non-privileged port (e.g., 8080 or 2080), which avoids the problem of forced re-binding of privileged ports.

@geomlattice
Copy link

I understand now. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment