May 13, 2023

Instant Linux VMs without the Installation

Typically, installing one of the major Linux distributions like Ubuntu or Debian requires going through an interactive installation wizard. This is great when you have a screen and keyboard but less ergonomic in a virtualized scenario, for example when you’re running in qemu/kvm.

I recently learned that, luckily, you don’t need the installation at all. As it turns out, all you need to run Linux is a root filesystem and a kernel. And what’s even better, you don’t need to fiddle around and source both components yourself, you can just use one of the cloudimg packages provided. These are the exact same images running on the big public cloud providers, so you can be assured that whatever you want to run on them works just fine.

As an example, let’s set up the latest Ubuntu 22.04 LTS cloudimg from the daily builds. For this, navigate to the release page and download the file named ubuntu-22.04-server-cloudimg-amd64.img (or the arm64 version if you’re running on Apple Silicon/ARM).

Looking up the initial problem yielded a very helpful script I modified for my use case. If you can’t install any additional packages, you can generate the user data image (containing the password and potentially files and scripts to run on init) on a different machine and simply copy it over. The only tools you need to have installed already are related to qemu.

Running the script will start up the VM and render the console as text, which is perfect for running in an SSH session. It will also expose the sshd server running inside the VM over the shared network on port 25354, you can sign in with the user ubuntu and the password you chose in the user data image.

#!/usr/bin/env bash

sudo apt-get install cloud-image-utils qemu

# This is already in qcow2 format.
img=ubuntu-22.04-server-cloudimg-amd64.img
if [ ! -f "$img" ]; then
  wget "https://cloud-images.ubuntu.com/releases/22.04/release/"

  # sparse resize: does not use any extra space, just allows the resize to happen later on.
  # https://superuser.com/questions/1022019/how-to-increase-size-of-an-ubuntu-cloud-image
  qemu-img resize "$img" +128G
fi

user_data=user-data.img
if [ ! -f "$user_data" ]; then
  # For the password.
  # https://stackoverflow.com/questions/29137679/login-credentials-of-ubuntu-cloud-server-image/53373376#53373376
  # https://serverfault.com/questions/920117/how-do-i-set-a-password-on-an-ubuntu-cloud-image/940686#940686
  # https://askubuntu.com/questions/507345/how-to-set-a-password-for-ubuntu-cloud-images-ie-not-use-ssh/1094189#1094189
  cat >user-data <<EOF
#cloud-config
password: asdfqwer
chpasswd: { expire: False }
ssh_pwauth: True
EOF
  cloud-localds "$user_data" user-data
fi

qemu-system-x86_64 \
  -enable-kvm \
  -cpu host \
  -smp 4 \
  -machine q35,accel=kvm,kernel-irqchip=split \
  -m 8G \
  \
  -drive "file=${img},format=qcow2" \
  -drive "file=${user_data},format=raw" \
  \
  -device intel-iommu,intremap=on \
  \
  -device virtio-net-pci,netdev=net0 \
  -netdev user,id=net0,hostfwd=tcp::25354-:22 \
  \
  -serial mon:stdio \
  -vga virtio \
  -nographic \
;

The qemu command is also configured to support nested virtualization within the VM, by handling the iommu. If you need to force quit the vm, use ctrl+a then x.

Once the vm is running, you can install all the packages you need. If you want to clone the vm, shut it down safely, then simply copy the file system, that’s it. It will take up some disk space, sure, but there’s a certain joy in copying an entire machine by duplicating a file.