Skip to main content

qemu-compose

qemu-compose aims to provide a docker-compose style composer for qemu command, with advanced scripting feature as well as http support for cloud init or general purpose provisioning.

Bring up a qemu VM by providing a qemu-compose.yml and run qemu-compose up

Advantages

  • Very simple and robust, written of several pure python scripts, depends on qemu commands only.
  • No libvirt stuff, no daemon process, no xml config, no complex abstraction, just a simple wrapper around qemu args.
  • support before_script and after_script for setup and cleanup
  • support boot_commands for vm provisioning (implemented using tty communication, gui not supported yet, and use jsonlisp for expressive power which apparently is turing-complete)
  • support http_serve for cloudinit
  • env interpolation for advanced configuration

Installation

$ pip install qemu-compose

Examples and Screenshot

Example 1: pull archlinux docker image and run archlinux image directly with systemd init just like normal vm boot, with ssh login setup:

$ qemu-compose pull --boot systemd --kernel /boot/vmlinuz-linux --initrd /boot/initramfs-linux.img --disk-size 256G dockerproxy.net/library/archlinux:latest
$ qemu-compose run dockerproxy.net/library/archlinux:latest

Demo:

asciicast

Example 2: download and bring up qemu vm with ubuntu cloudimg disk file, run a interactive shell

$ cd ./examples/ubuntu-cloudimg__amd64/
$ qemu-compose up

Demo:

asciicast

Pull OCI/Docker Images

qemu-compose pull imports an OCI/Docker image into the local qemu-compose image store as a bootable qcow2 image.

The imported image is stored under:

~/.local/share/qemu-compose/image/<sha256>/

and can then be listed, tagged, removed, or run with the normal image commands:

$ qemu-compose images
$ qemu-compose run IMAGE[:TAG]
$ qemu-compose tag SOURCE_IMAGE TARGET_IMAGE
$ qemu-compose rmi IMAGE

Dependencies

The pull/import path uses external tools:

  • skopeo: pull/copy OCI images from registries
  • umoci: unpack OCI images
  • qemu-img: create qcow2 disks
  • guestfish: format and populate the qcow2 root filesystem
  • openssl: hash --root-password values

On Arch Linux:

$ sudo pacman -S --needed skopeo umoci qemu-img libguestfs openssl

Registry proxies are configured through the normal environment variables used by skopeo, for example:

$ HTTPS_PROXY=http://127.0.0.1:8123 qemu-compose pull ...

Basic Container Boot

Docker/OCI images are root filesystems, not full VM disks. They do not normally include a bootloader, kernel, or initramfs, so pull requires direct-boot assets:

  • --kernel: Linux kernel file copied into the qemu-compose image
  • --initrd: initramfs file copied into the qemu-compose image

Example:

$ qemu-compose pull \
    --kernel /boot/vmlinuz-linux \
    --initrd /boot/initramfs-linux.img \
    --disk-size 512M \
    alpine:3.20

By default, --boot container is used. qemu-compose injects /qemu-compose-init into the rootfs and boots with:

init=/qemu-compose-init

That init script mounts basic pseudo-filesystems and runs the OCI image's Entrypoint/Cmd. This is useful for lightweight container-like VM images.

Systemd Boot

Use --boot systemd for images that contain systemd, such as Arch Linux:

$ qemu-compose pull \
    --boot systemd \
    --kernel /boot/vmlinuz-linux \
    --initrd /boot/initramfs-linux.img \
    --disk-size 2G \
    dockerproxy.net/library/archlinux:latest

In systemd mode, qemu-compose prepares the rootfs for a normal serial-console VM boot:

  • boots init=/usr/lib/systemd/systemd
  • writes /etc/fstab for /dev/vda1
  • clears /etc/machine-id so systemd regenerates it
  • enables serial-getty@ttyS0.service when available
  • writes a DHCP systemd-networkd profile and enables systemd-networkd when available
  • enables systemd-resolved, sshd, and qemu-guest-agent when those units exist
  • masks systemd-imds-generator when present to avoid noisy minimal-image boot warnings

This mode makes an OCI Arch Linux rootfs boot to a normal systemd multi-user login prompt, but it does not install missing packages. If the image does not contain systemd or SSH packages, pull will not add them.

Root Login

By default, pull unlocks root with an empty password so serial console login works immediately:

archlinux login: root
Password:

Press Enter at the password prompt.

To set a real root password instead:

$ qemu-compose pull \
    --boot systemd \
    --root-password testpass \
    --kernel /boot/vmlinuz-linux \
    --initrd /boot/initramfs-linux.img \
    dockerproxy.net/library/archlinux:latest

--empty-root-password is also accepted explicitly, but it cannot be used together with --root-password.

Updating Existing Imports

The image directory name is based on the OCI image digest. If the digest is already present, use --force to rebuild it with different options:

$ qemu-compose pull --force --boot systemd ...

Use --keep-workdir to keep temporary import files when debugging a failed import.

Limitations

  • pull currently creates one ext4 root partition at /dev/vda1.
  • It uses direct kernel boot, not GRUB/UEFI boot inside the imported disk.
  • Container boot mode runs the OCI command; systemd boot mode boots systemd, but does not install extra packages.
  • Full devbox images with custom packages, dotfiles, certificates, GRUB, or cloud-init still require a provisioning/build layer on top of the imported rootfs.

SSH Helper

qemu-compose provides a helper to invoke ssh with the instance key and safe defaults.

  • Usage: qemu-compose ssh [OPTIONS] VMID COMMAND [ARG...]
  • Defaults added by qemu-compose:
    • -o StrictHostKeyChecking=no
    • -o UserKnownHostsFile=/dev/null
    • -i ~/.local/share/qemu-compose/instance/VMID/ssh-key
    • appends root@vsock%<cid> as the default destination (falls back to root@vsock%${cid} if CID is unknown)

Examples:

# Print the default ssh command that would be used for a given VMID
$ qemu-compose ssh <vmid>
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.local/share/qemu-compose/instance/<vmid>/ssh-key root@vsock%<cid>

# Connect using vsock destination (cid recorded when the VM booted)
$ qemu-compose ssh <vmid> root@vsock%<cid>

# Connect over TCP instead (override destination and add your own options)
$ qemu-compose ssh <vmid> -p 2222 root@127.0.0.1

# Run a remote command
$ qemu-compose ssh <vmid> root@vsock%<cid> uname -a

Notes:

  • The instance key is generated at first boot and stored under ~/.local/share/qemu-compose/instance/<vmid>/ssh-key.
  • Any ssh options you pass will be forwarded; the last-specified option wins.

Down Command

Stop and remove a VM instance.

$ qemu-compose down [identifier] [options]

Options:

  • identifier: Instance ID, unique prefix, or assigned name (optional if config file exists)
  • -f, --file FILE: Compose configuration file to parse for instance name
  • --force: Force removal without confirmation

Examples:

# Stop and remove instance by name
$ qemu-compose down my-vm

# Use config file to auto-detect instance name
$ cd ./examples/ubuntu-cloudimg__amd64/
$ qemu-compose down

# Use specified config file
$ qemu-compose down -f /path/to/qemu-compose.yml

# Force removal
$ qemu-compose down --force my-vm

Note: If no identifier is provided, qemu-compose will look for qemu-compose.yml or qemu-compose.yaml in the current directory and use the name field as the instance identifier.

Tag Command

Create a tag that refers to an image, similar to docker tag.

$ qemu-compose tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
  • SOURCE_IMAGE[:TAG]: Source image identifier (can be image ID, name, or name:tag)
  • TARGET_IMAGE[:TAG]: Target image name and optional tag (defaults to latest if not specified)

Behavior:

  • If the target tag already exists on another image, it will be moved to the source image
  • Tags are stored in the image's manifest.json file under the repo_tags field

Examples:

# Tag an image by ID
$ qemu-compose tag 94a0434d0f73 devbox:archlinux

# Tag an image by name
$ qemu-compose tag my-image:v1 my-image:latest

# Create a new tag for an existing image
$ qemu-compose tag ubuntu:20.04 ubuntu:focal

# Replace an existing tag (moves tag from one image to another)
$ qemu-compose tag new-image:v1 existing-tag:latest

Notes:

  • Image IDs can be specified as full SHA256 digest or unique prefix
  • When a tag already exists on a different image, the tag is moved (not copied) to the new image
  • Use qemu-compose images to list all images and their tags

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qemu_compose-1.1.1.tar.gz (122.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qemu_compose-1.1.1-py3-none-any.whl (151.1 kB view details)

Uploaded Python 3

File details

Details for the file qemu_compose-1.1.1.tar.gz.

File metadata

  • Download URL: qemu_compose-1.1.1.tar.gz
  • Upload date:
  • Size: 122.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for qemu_compose-1.1.1.tar.gz
Algorithm Hash digest
SHA256 a09afcd8527041df7adf71eb456791666ba68322bc60e21992f84dda03bfc7fd
MD5 9f25697ef63fe1ae78220c906a938ca5
BLAKE2b-256 8408090ced234680e28d438bc6b9797356d727f3785747ec328c512e07dfc1a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for qemu_compose-1.1.1.tar.gz:

Publisher: pypi.yml on zTrix/qemu-compose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file qemu_compose-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: qemu_compose-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 151.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for qemu_compose-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2883d7adb6aa39b6f2817879aee261ffa331e7c6cde8ced4efccf6008c33931c
MD5 df65fbf97ae1b14f627b7a3a543e359c
BLAKE2b-256 786e98846fcb3dd959f6d14f36acd80aa84cba1be577463d1f405c2d42838c3c

See more details on using hashes here.

Provenance

The following attestation bundles were made for qemu_compose-1.1.1-py3-none-any.whl:

Publisher: pypi.yml on zTrix/qemu-compose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.1.1 This release

2 files

1.1.0

2 files

0.9.2

2 files

0.9.0

2 files

0.8.7

2 files

0.8.6

2 files

0.8.5

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.0

2 files

0.7.2

2 files

0.7.1

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page