Skip to content

Repository files navigation

qontainer

qontainer = QEMU + Container

Run QEMU based virtual machine in a Container native way.

The VM owns the container's (or pod's) IP address: it ARPs, answers pings, serves SSH — as if the VM itself were the container.

With Docker

# With Ubuntu (default ID/PW is deploy/deploy)
$ docker run --rm -it\
    --device=/dev/kvm:/dev/kvm --device=/dev/net/tun:/dev/net/tun\
    --cap-add NET_ADMIN \
    -e VM_CPU=2\
    -e VM_MEMORY=2G\
    -e VM_DISK="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64-disk-kvm.img" \
    -e VM_DISK_SIZE=25G\
    -e NETWORK_TYPE=bridge\
    -v $PWD:/data\
    ghcr.io/iwanhae/qontainer:latest

# With Alpine (tested; default ID/PW is deploy/deploy)
$ docker run --rm -it\
    --device=/dev/kvm:/dev/kvm --device=/dev/net/tun:/dev/net/tun\
    --cap-add NET_ADMIN \
    -e VM_CPU=2\
    -e VM_MEMORY=1G\
    -e VM_DISK="https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/nocloud_alpine-3.22.1-x86_64-bios-cloudinit-r0.qcow2" \
    -e GUEST_SHELL=/bin/sh\
    -e NETWORK_TYPE=bridge\
    -v $PWD:/data\
    ghcr.io/iwanhae/qontainer:latest

# With Amazon Linux 2 (default ID/PW is deploy/deploy)
$ docker run --rm -it\
    --device=/dev/kvm:/dev/kvm --device=/dev/net/tun:/dev/net/tun\
    --cap-add NET_ADMIN \
    -e VM_CPU=2\
    -e VM_MEMORY=2G\
    -e VM_DISK="https://cdn.amazonlinux.com/os-images/2.0.20231101.0/kvm/amzn2-kvm-2.0.20231101.0-x86_64.xfs.gpt.qcow2" \
    -e VM_DISK_SIZE=25G\
    -e NETWORK_TYPE=bridge\
    -v $PWD:/data\
    ghcr.io/iwanhae/qontainer:latest

Any cloud image with cloud-init and a DHCP client works (Ubuntu, Alpine, Amazon Linux, ...). The guest gets its network configuration via DHCP, not via distro-specific config files.

With Kubernetes

It is even working on Kubernetes with Cilium. I believe you know what to do ¯\_(ツ)_/¯

How bridge mode works

container netns (this process)              guest VM
┌─────────────────────────────────┐        ┌──────────────────────┐
│ eth0 (IP removed; plain wire)    │        │ eth0 (MAC 52:xx:...) │
│  └─ br0 (no IP) ── tap0 (qontainer-owned) ─┤  dhcp client         │
│                                 │  DHCP  │                      │
│ embedded DHCP server (UDP 67)   │◄───────┤  gets the pod IP,    │
│  single lease: VM MAC -> pod IP │        │  gateway, DNS,       │
│ ebtables DNAT rule (/32 only)   │        │  default route       │
└─────────────────────────────────┘        └──────────────────────┘
  • The container's eth0 is enslaved to br0 and its IP is handed over to the VM, which claims it via DHCP (including RFC 3442 classless static routes, so /32 pod IPs with out-of-prefix gateways work).
  • qontainer embeds its own minimal DHCPv4 server, so no external binary is needed and it works on an unnumbered bridge where dnsmasq cannot.
  • The guest's cloud-init payload is just dhcp4: true (matched by MAC) — no distro-specific rendering, no bootcmd hacks.
  • If the DHCP server dies, the VM is terminated with it: the container restarts instead of limping with an un-renewable lease (fail fast).
  • On Cilium (/32 addresses), an ebtables rule rewrites the destination MAC that eBPF assigned, to the VM's MAC.

Environment Variables

# CPU Cores
VM_CPU="2"
# Memory
VM_MEMORY="2G" 
# Default disk image path, if URL is provided, will downloads it and save to `/data/disk.img`
VM_DISK="/data/disk.img"
# Resize the disk to this. Can not shrink. Expand only.
VM_DISK_SIZE="25G"

# `user` > (default) NAT by QEMU; guest gets a private IP via slirp's DHCP
# or 
# `bridge` > The VM will own the container's (or pod's) IP address,
#            provided by the embedded DHCP server
NETWORK_TYPE="bridge" 
# Changing this not recommended
NETWORK_INTERFACE="eth0"
# Changing this not recommended; Will use container's IP if NETWORK_TYPE="bridge"
NETWORK_ADDRESS="172.17.0.2/16"
# Changing this not recommended; Will use container's default route if NETWORK_TYPE="bridge"
NETWORK_DEFAULT_GATEWAY="172.17.0.1"
# Changing this not recommended. Will use container's NSs if NETWORK_TYPE="bridge"
NETWORK_NAMESERVERS="10.43.0.10,8.8.8.8,8.8.4.4"
# Changing this not recommended. Will use container's Search if NETWORK_TYPE="bridge"
NETWORK_SEARCH="default.svc.k8s.iwanhae.kr.,svc.k8s.iwanhae.kr.,k8s.iwanhae.kr."
# Will use randomly generated MAC addr if not set
NETWORK_MAC_ADDRESS="52:de:4d:c0:72:09"

# default shell for the default user
GUEST_SHELL="/bin/bash"
# by default, will use container's hostname 
GUEST_HOSTNAME="c0a47fe410cb"
# name of default user
GUEST_USERNAME="deploy"
# encrypted password of the default user
# default value is "deploy" (so the default ID and PW is "deploy:deploy", only accessible via console)
GUEST_PASSWORD="$6$rounds=4096$KUjo2cumnYaz0fmk$EsoVV1xP/FXIkv5mm4V26CR3qJrDZhs3Rga8OfBKNBUSsmCM7OHouHMHHz8lApGsD835DqpFvAgqJv1Hq5J.k0"
# default user's SSH authorized keys
GUEST_SSH_AUTHORIZED_KEYS=[]
# default user's SUDO policy
GUEST_SUDO="ALL=(ALL) NOPASSWD:ALL"
# base64 encoded user script that will be run at first boot.
# e.g., "ZWNobyBoZWxsbyB3b3JsZA=="
GUEST_USERSCRIPT_BASE64="" 

# Changing this not recommended
QEMU_EXECUTABLE="/usr/bin/qemu-system-x86_64"

About

QEMU in Docker, but in Container Native way!

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages