← Back

My headless VM setup

August 16, 2025

Part of life is the constant morphing, looking for the optimal shape in life that serves as a scaffolding for the next phase of life. Anyhow, this post isn't about life but about my isolated virtual environment on my host machine, and the different setup versions I had to try before settling for the last one.

First, why? Exactly the same question I asked myself halfway into my second attempt after the first setup blatantly failed. Here's the same response the second voice in my head told me; the nature of your work for the next 6 months would possibly crash your system, meaning you'd have to do fresh reinstall of this Linux distro (Ubuntu) running on your Dell XPS 13. Therefore, isolate it inside a virtual machine, where consequences ain't that detrimental.

I planned to keep this post short, so let's get started. I will walk you through the attempted setups for a VM, starting with:

  • Nested Virtualisation with Docker: I knew from the onset that this wasn't what my supposed setup should be based on. As I won't get near-experience as running code on direct kernel environment—so I read. Also, I have had difficulties with Docker networking in the past, and I wasn't ready for that, at least for now. So why did I even attempt? Good question, assuming you asked. Currently, I have a beautifully working such environment on my machine from the Linux Kernel Labs. The scripts involved in this setup were complicated, and looked out of grasp for my current knowledge. And I'm guessing you would say use LLM to figure it out. Piece of advice, if you don't appreciate not knowing when you're being feed erroneous information, avoid relying on it, whether solely or 75%, for works like this. Skill issues, you would say, yeah I agree but your LLM reliance is also a skill issue. Anyways, I did try to read the scripts and replicate the environment, and at some point it looked like I did figure it out, but once I got terminal access to the environment, I was faced with sudo permission issues for commands as simple as mkdir and others. I knew there was more to it, but I already lost interest to pursue it as I knew from the very beginning that this wasn't my ideal desired environment anyways, so why bother.

  • Graphical VM: Not until it 'started' that I figured it wasn't what I was looking for as a setup. Prior before the start of this setup, I didn't know anything about headless VM, shit, there was a lot and still a lot I didn't know about VM setup. But I know what I wanted, a nongraphical setup with only a terminal access. Reluctantly completed the setup. By now, I have enabled virtualization, installed QEMU and KVM alongside tools like libvirt, virt-manager and others. Used virt-manager to create a VM running Ubuntu Desktop. Had it working but having a full graphical VM inside another full graphical environment just looked so wrong to me. Complained about the displeasure to my friend who was on a video call with me whilst I was at it. Okay, that last information wasn't necessary, but yeah. After setting up tis setup, I did more readings and then discovered two new things plus an old one: minimal installation, headless VM, and server-version of distros. Funny thing is that I knew about the server version of distros, but never occurred to me as an option I could explore for my setup.

  • Headless VM: By now, I had a better understanding of what I was gonna chase down next. Did a lot of reading regarding headless VM setup, as it appeared almost everyone was being forced to do a graphical VM setup. Luckily, I found a reply to a headless VM setup question on…can't remember, suggesting the use of the Debian Netinstaller ISO, together with just qemu- commands, with no mention of the extraneous tools I had earlier installed. I'm yet to delete those. Anyways, this is the setup I will detail for you throughout the rest of this post; to have a headless VM setup for whatever purpose it was meant to serve you.

Setup for headless VM

This entire setup was ran on Ubuntu 24.04 LTS, and I can't assure you any backward compatibility with previous LTSs. First thing you'd wanna do is customary update of your system's packages.

sudo apt update
sudo apt upgrade

The qemu-kvm package is what setups up your entire system to be a virtualisation host. It serves as a 'meta-package', pulling in all the other packages needed as dependencies.

sudo apt install qemu-kvm

This will install two packages, qemu-system-x86_64 and qemu-img, that are important for this setup alongside others. It also setups up the kvm user group and permissions, so run the following to append your user to it as well:

sudo usermod -aG kvm $USER

Running the QEMU/KVM virtual machine requires access to the special character device, /dev/kvm, which is controlled by the Linux kernel. And by default, only the root user and members of the kvm group can access this device. So you understand why it is at least necessary.

You need a ISO image file; the OS your VM would run. And for this particular setup, I'm using Debian Netinstall ISO, and unfortunately for you, that what you would use as well, unless you like being defiant.

wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.5.0-amd64-netinst.iso

After downloading that, your next preliminary step is to create the virtual hard disk that'll house and act as the VM's hard drive. Also, it will be based on the qcow2 format; only grows when data is written to it.

qemu-img create -f qcow2 \<name\>.qcow2 20G

You can choose to adjust the memory size, but so far so good, 20G would serve me (you) for now, until when it doesn't.

Now comes the critical step. This is where we start the VM with the installer ISO attached, and redirect its console to your terminal:

qemu-system-x86_64 \ 
    -machine accel=kvm \
    -m 4G -nographic \
    -hda \<name\>.qcow2 \
    -cdrom debian-13.0.0-amd64-netinst.iso \
    -boot d

This starts the installation process of the Debian ISO on the virtual disk. You can adjust the allocate RAM size, which is 4G in my case, to say 2G or even higher.

Please note that as soon as the VM starts (you will see some text), quickly press the ESC key to interrupt the automatic boot process. This gives you the boot: prompt. Right at this prompt, type console=ttyS0 and press Enter. This instructs the installer to setup the Linux kernel to use the first serial port (ttyS0) as its primary console, which QEMU will display directly in your host terminal.

At this point, you see the standard Debian text-based installer, proceed as necessary with your preferences. In my case, I was quite interested in installing the "SSH server" option to enable remote development on it using the Zed editor.

It is a long installation process, and I can't detail everything in this post, so bear with me if you wanted to be hand-held walking through it.

Now, main event; running the new headless VM. With the Debian installed on the virtual disk, we can run the VM normally, but we'll tweak the qemu- command to add networking and SSH access (remember, it is part of my own setup):

qemu-system-x86_64 \
    -machine accel=kvm \
    -m 4G -nographic \
    -hda \<name\>.qcow2 \
    -netdev user,id=net0,hostfwd=tcp::2222-:22 \
    -device e1000,netdev=net0

The new flags; -netdev sets up a user-mode networking, and then forwards port 2222 on your host machine to port 22 inside the VM. The -device creates a virtual intel E1000 network card, and connects it to the network we just defined. By now, given that you've ran the command, you should see a login prompt, specify the user you defined whilst installing the Debian ISO, and in the next prompt, the password for the user.

There you have it, a working headless VM setup. Did I hear "thank you"? Ooh, you're welcome. laughs

You can abstract all these nicely into a bash script, which is what I did and you can find it at 0xull/foothold-labs