It turned out that it can perfectly does that. Here is a random recipe to have a working Ubuntu, without having to use grub or any bootloader. Note that ubuntu first boot is still utterly slow and bloated (snap and cloud-init, I’m looking at you !).
- First download ubuntu rootfs + initramfs + kernel here.
For our usage you will need :
focal-server-cloudimg-amd64-root.tar.xz: the rootfs you will unpack into a brand new disk that we will create
focal-server-cloudimg-amd64-vmlinuz-generic: a kernel
Here is the script to get it all
#!/bin/bash wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64-root.tar.xz wget https://cloud-images.ubuntu.com/focal/current/unpacked/focal-server-cloudimg-amd64-initrd-generic wget https://cloud-images.ubuntu.com/focal/current/unpacked/focal-server-cloudimg-amd64-vmlinuz-generic
- We then need to create a disk to put our rootfs content into it:
# create a 4G file dd if=/dev/zero of=focal-rootfs.ext4 bs=1 count=0 seek=4G # create an ext4 partition of this file, yes you can format in ext4 a simple file, how awesome. mkfs.ext4 -F -L linuxroot focal-rootfs.ext4 # if not root you will need root access for this command mount -o loop focal-rootfs.ext4 /mnt # put the content of the rootfs, into the partition. tar -C mnt/ -xJf focal-server-cloudimg-amd64-root.tar.xz # putting our ssh public key in order to connect easily to the VM. mkdir -p /mnt/home/ubuntu/.ssh/ cat ~/.ssh/ed_25519.pub >> /mnt/home/ubuntu/.ssh/authorized_keys # and of course, unmount it. umount mnt/
And finally our
qemu-system-x86_64 \ -enable-kvm \ -kernel focal-server-cloudimg-amd64-vmlinuz-generic \ -m 4G \ -boot c \ -append 'root=/dev/vda rw console=ttyS0' \ -device e1000,netdev=net0 \ -netdev user,id=net0,hostfwd=tcp::5555-:22 \ -drive if=virtio,format=raw,file=focal-rootfs.ext4 \ -initrd focal-server-cloudimg-amd64-initrd-generic \ -nographic
-enable-kvm: obviously, we want a VM that runs with the CPU virtual instructions.
-kernelspecify which kernel to use. This means you can use whichever kernel you want (if you decide to compile your own).
-m 4G4Gio of memory allocated to this VM.
-boot cboot to disk (don’t try to boot to network or something fancy).
-append: append the string to the kernel when booted. In our case, we are telling the kernel our disk is a
/dev/vdaand read-write (
rw). And to output the console to serial. S0 (
ttyS0), qemu will foward
ttyS0to your terminal (stdout).
-device -netdev: add a network interface to the system, with a shenigan to have the port 22 forwarded to the port 5555 on localhost. Which will allow us to connect via ssh easily.
-drive ...: add a virtio disks to the VM. This is for this reason that you need to pass
/dev/sdato the kernel since it knows that the disk is a virtio device.
-initrd: nothing to say about the ramfs, but you can have your own too.
I have no idea whatsoever if this will come handy one day, but I struggled a bit with qemu and ubuntu since the documentation was sparse. The invocation doc helps a lot though.