Build an installation process using QEMU. Depending on your use case, this can do anything from testing the install process to automating real mass-installations. For other installation topics, see DebianInstaller.
Basic usage
To install the amd64 version of Debian in a virtual machine ("VM"), install qemu-system-x86, download the stable amd64 netinst CD image, then do:
# Create a disk image:
qemu-img create -f qcow2 hd_image.qcow2 10G
# run with minimal options:
qemu-system-x86_64 \
-m 1G \
-drive file=hd_image.qcow2 \
-cdrom <your-iso-image>
The installer should start in a window. You can install a Debian system in hd_image.qcow2 now, but it will be very slow, and will assume you're using an older PC that uses BIOS instead of UEFI. Close the window and try this instead:
# Delete and recreate your disk image:
rm hd_image.qcow2
qemu-img create -f qcow2 hd_image.qcow2 10G
# Create a file to store UEFI variables in:
cp /usr/share/OVMF/OVMF_VARS_4M.fd ./
# run with some new options:
qemu-system-x86_64 \
-m 1G -cpu max -enable-kvm \
-no-reboot \
-device virtio-net-pci,netdev=net0 -netdev user,id=net0 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd \
-drive if=virtio,cache=unsafe,file=hd_image.qcow2 \
-device ide-cd,drive=cdrom,bootindex=0 \
-drive if=none,id=cdrom,format=raw,file=<your-iso-image>
Here's what all the options mean:
- -m 1G
give the VM one gigabyte of memory (slightly more than the minimum memory requirement)
- -cpu max
- emulate the fastest available CPU
- -enable-kvm
use KVM virtualisation (you may need to enable virtualization in your BIOS)
- -no-reboot
- exit when the installer tries to reboot
- -device virtio-net-pci,netdev=net0 -netdev user,id=net0
- use a fast network device
- -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd
- boot with UEFI instead of BIOS
- -drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd
- store UEFI variables in the specified file
- -drive if=virtio,cache=unsafe,file=hd_image.qcow2
- use a fast interface and cache settings (risks corrupting the disk on unclean exit, but you can just restart the installer)
- -device ide-cd,drive=cdrom,bootindex=0
- pretend the CD-ROM is attached to an IDE bus, and boot from it
- -drive if=none,id=cdrom,format=raw,file=<your-iso-image>
use your CD-ROM as a raw disk (not a .qcow2), attached to the IDE bus from the previous line
Finally, try the following advanced technique:
# Extract the Linux kernel and the initrd for the text-based installer:
sudo mount -o loop,ro <your-iso-image> /mnt
cp /mnt/install.amd/{initrd.gz,vmlinuz} ./
sudo umount /mnt
# Delete and recreate your disk image:
rm hd_image.qcow2
qemu-img create -f qcow2 hd_image.qcow2 10G
# run with more new options:
qemu-system-x86_64 \
-m 1G -cpu max -enable-kvm \
-no-reboot \
-device virtio-net-pci,netdev=net0 -netdev user,id=net0 \
-nographic -serial mon:stdio -echr 8 \
-kernel vmlinuz -initrd initrd.gz \
-append "console=ttyS0,115200n8" \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd \
-drive if=virtio,cache=unsafe,file=hd_image.qcow2 \
-drive if=virtio,format=raw,file=<your-iso-image>
The installer should run in a terminal without showing a boot menu. It will start a screen session, so you can e.g. type ctrl-a <space> to cycle between screens. You can also toggle between normal mode and the QEMU monitor with ctrl-h c. Here's what the new options mean:
- -nographic
- do not display QEMU's graphical interface
- -serial mon:stdio
- redirect the VM's serial port to the QEMU monitor, and display the monitor on standard input/output
- -echr 8
change QEMU's escape character from ctrl-a to ctrl-h (h is the 8th letter of the alphabet - change the number to your preferred letter)
- -kernel vmlinuz -initrd initrd.gz
boot using the previously-extracted kernel and initrd files
- -append "console=ttyS0,115200n8"
- run the installer on the VM's serial port, with the fastest settings
- -drive if=virtio,format=raw,file=<your-iso-image>
- use a fast interface for your CD-ROM, and don't boot from it
To exit, type ctrl-h c then quit <enter>. If your terminal thinks it's only 25 lines tall after exiting qemu, run reset to fix it
Running the installer in a terminal means you can copy and paste between the VM and the host. Adding -kernel, -initrd and -append lets you change the initial root filesystem and kernel command-line without having to edit the ISO image. For more initrd and command-line options, see boot/grub/grub.cfg in your ISO image.
Basic usage for i386
{i} i386 support was removed in in trixie, so this is only available in bookworm and earlier.
Just change qemu-system-x86_64 to qemu-system-i386; and get an i386 image, kernel and initrd. Everything else should work as normal.
If your i386 device uses BIOS instead of UEFI, you will also need to remove the two OVMF_VARS lines.
Basic usage for ppc64el
First, change qemu-system-x86_64 to qemu-system-ppc64el; remove the two OVMF_VARS lines; and get a ppc64el image, kernel and initrd.
Then find this line:
-m 1G -cpu max -enable-kvm \
... and remove -enable-kvm:
-m 1G -cpu max
Basic usage for mips64el, mipsel, arm64, armhf and riscv64
{i} riscv64 support was added in trixie, so was only available in testing at the time of writing.
As of trixie, the CD images for these architectures don't work with qemu - use netboot instead.
First, change qemu-system-x86_64 to qemu-system-<architecture>; remove the two OVMF_VARS lines; and get an appropriate kernel and initrd.
Next, find this line:
-m 1G -cpu max -enable-kvm \
... for mips64el, change -cpu max to -cpu 5KEc and replace -enable-kvm with e.g. -M malta:
-m 1G -cpu 5KEc -M malta \
... or for mipsel, remove -cpu and replace -enable-kvm with e.g. -M malta:
-m 1G -M malta \
... or for arm64, armhf and riscv64, just replace -enable-kvm with -M virt:
-m 1G -cpu max -M virt \
Finally, unless you're using riscv64, remove this line:
-append "console=ttyS0,115200n8" \
Install to a physical disk
QEMU can install install directly to a target device, or install indirectly with a backing file. The former is simpler and may be a little faster, the latter queues up changes to be committed when you're ready.
Typing the wrong device name can destroy all information on your hard disk, so follow these instructions to select the right device name:
# Make sure your device is disconnected or turned off, then do:
ls /dev/disk/by-id/* | grep -v -- '-part[0-9]*$' | tee /tmp/disks.txt
# Make sure your device is connected and powered up, then do:
ls /dev/disk/by-id/* | grep -v -- '-part[0-9]*$' | diff /tmp/disks.txt -
# You should see something like:
# 10a11
# > /dev/disk/by-id/<identifier-of-your-disk>
# Make a variable with the device from the previous command:
TARGET_DEVICE=/dev/disk/by-id/<identifier-of-your-disk>
# Check you typed it correctly by reading one byte from the disk:
sudo head -c1 "$TARGET_DEVICE" > /dev/null
# Disconnect your device, then try again:
sudo head -c1 "$TARGET_DEVICE" > /dev/null
If you did everything right, the head command should only work when the device is plugged in. You can now use $TARGET_DEVICE instead of the name of your device in future commands.
The instructions below often refer to $TARGET_DEVICE. If you create a variable as described above, you can paste those exact instructions. Otherwise, change $TARGET_DEVICE to your actual device name.
Install directly to a target device
The simplest method is just to make your target device available in the VM. Find this line:
-drive if=virtio,cache=unsafe,file=hd_image.qcow2 \
... and change file=hd_image.qcow2 to format=raw,file="$TARGET_DEVICE":
-drive if=virtio,cache=unsafe,format=raw,file="$TARGET_DEVICE" \
... then install Debian in the usual way.
{i} You may also need to run qemu with sudo so it can access the disk.
Install indirectly with a backing file
Qemu lets you create a .qcow2 image that's backed by another file. That means it writes to the .qcow2 image as normal, but when it tries to read from a part of the image that hasn't been written to yet, the read falls through to the equivalent part of the backing file. So the installer sees something that looks like the target disk, but can only queue up changes until you explicitly commit them.
{i} You may need to run the commands below with sudo so they can access the disk.
First, create a .qcow2 image backed by your target disk:
qemu-img create -f qcow2 hd_image.qcow2 -F raw -b "$TARGET_DEVICE"
Then install Debian on hd_image.qcow2 in the usual way.
Once the install is complete, Debian should be installed on hd_image.qcow2, but $TARGET_DEVICE should be unchanged. If you'd like to try out your Debian system in a VM, do:
qemu-system-x86_64 \
-m 1G -cpu max -enable-kvm \
-no-reboot \
-device virtio-net-pci,netdev=net0 -netdev user,id=net0 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd \
-drive if=virtio,file=hd_image.qcow2
If anything went wrong, just delete hd_image.qcow2 and try again. Otherwise, commit your changes to $TARGET_DEVICE:
qemu-img commit -p -d hd_image.qcow2
rm hd_image.qcow2
(-p shows a progress bar, -d skips some work that isn't needed if you're about to delete the file)
{i} The progress bar might pause for several minutes at a time, as data is quickly queued up then slowly written to disk
Advanced techniques
The basic usage lets you install a system using a VM, advanced techniques can be more efficient and more widely useful.
Configure UEFI manually
If you delete your ./OVMF_VARS_4M.fd file after installing your system, you'll need to reconfigure it manually before you can boot into your VM again:
do cp /usr/share/OVMF/OVMF_VARS_4M.fd ./ again
run qemu with the normal settings
when you get to a Shell> prompt, type exit and press <enter>
go to Boot Maintenance Manager > Boot Options > Add Boot Option
select EFI, > EFI > debian > grubx64.efi
press <enter> to input the description, type Debian, and press <enter>
Commit Changes and Exit
Change Boot Order
press <enter> to change the order
select Debian
press + until Debian is at the top
press <enter>
Commit Changes and Exit
press <escape> twice
Reset
Your system should now boot normally.
Netboot without a CD image
The normal installation method ("netinst") requires a CD image, the netboot method downloads everything instead. This might be more convenient if you're testing the installer against recently-updated packages, or you're installing from a system without much disk space.
First, download the netboot kernel and initrd:
go to the installation page
- click the "other images" section for your architecture
if there is a netboot/ directory (most architectures)...
if you want a graphical installer, click gtk/
if there is a debian-installer/ directory (most architectures)...
click debian-installer/
- click your architecture
download initrd.gz and either linux, vmlinuz or vmlinux (whichever is present)
otherwise, if there are directories with different machine types (e.g. mips64el)...
- click your machine type
click netboot/
download vmlinuz-* and initrd.gz
Then find the following lines in your qemu command:
-kernel vmlinuz -initrd initrd.gz \
-drive if=virtio,format=raw,file=<your-iso-image>
... change vmlinuz and initrd.gz to the name of the kernel and initrd files you downloaded, and remove the -drive line altogether:
-kernel linux -initrd initrd.gz \
Running your qemu command should now start the netboot installer.
Cache downloads
The installer can download packages through a proxy server. This can save time and bandwidth if you run the installer frequently.
First, install apt-cacher-ng on your host (or some other device on your network).
Next, find the following line in your qemu command:
-append "console=ttyS0,115200n8" \
... and add mirror/http/proxy=<url>
-append "console=ttyS0,115200n8 mirror/http/proxy=<url>" \
qemu assigns the address 10.0.2.2 to the host machine by default, and apt-cacher-ng uses port 3142 by default, so you would usually use mirror/http/proxy=http://10.0.2.2:3142/.
{i} If you use preseeding, you can add mirror/http/proxy=<url> there instead.
Preseed your answers
You can use preseeding to define default answers to questions.
First, install cpio and create a preseed file called preseed.cfg.
Next, merge it into a new initrd:
# Add your preseed file to the initrd:
{ cat initrd.gz; echo preseed.cfg | cpio --format=newc --create | gzip -c; } > initrd-custom.gz
{i} You can add other files the same way, such as udeb files to use during installation, scripts to call from preseed/early_command and preseed/late_command, or installer hooks.
To use your preseed file, find these lines:
-kernel vmlinuz -initrd initrd.gz \
-append "console=ttyS0,115200n8" \
... and change them to something like:
-kernel vmlinuz -initrd initrd-custom \
-append "console=ttyS0,115200n8 auto" \
This will use your modified initrd-custom file and run the installer in automated mode.
Install to an in-memory disk
If you have enough memory on your computer and want the installer to run faster, you can install to a device that only exists in memory.
First, create your device (note: /tmp has been a tmpfs since trixie):
mkdir -p /tmp/installer
# not required in trixie and later:
sudo mount -t tmpfs tmpfs /tmp/installer
TARGET_DEVICE=/tmp/installer/installer.img
truncate -s 10G "$TARGET_DEVICE"
Then install using the Direct install instructions, above.
{i} truncate -s creates a sparse file. This should be slightly faster than a .qcow2 file, but doesn't support snapshots.
Use the qemu monitor
You can toggle between normal mode and the QEMU monitor with ctrl-h c. Some commands are particularly useful when testing the installer:
To debug the installer kernel, run gdbserver in the monitor, then call gdb --eval-command='target remote localhost:1234' on the host.
To rerun a problematic section of the installer, run savevm, loadvm and delvm in the monitor to store and roll back to specific snaphots.
{i} Snapshots require all attached devices to use .qcow2. If you want to install to an in-memory disk, you'll have to use an in-memory .qcow2 disk.
Communicate between host and VM
To make host files available in the installer's filesystem (e.g. udebs), copy them the same way as preseed.cfg.
You can just copy and paste data in the terminal. The installer includes base64, which makes it easier to copy and paste binary files.
How do I copy d-i logfiles to a remote host? provides general information about communicating with the installer, but here are some qemu-specific notes:
switch to a shell with ctrl-a <space> instead of ctrl-alt-F2
by default, the VM thinks your host has IP address 10.0.2.2
- by default, the host cannot connect to the VM (see below)
Connect from the host to the VM
The host can only connect to the VM on ports that you explicitly forwarded.
Find the following line in your qemu command:
-device virtio-net-pci,netdev=net0 -netdev user,id=net0 \
... and append e.g. ,hostfwd=tcp::5555-:80 to forward port 5555 on the host to port 80 on the VM:
-device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:80 \
Now anything in the VM that listens on 10.0.2.2:80 will be accessible from the host on 127.0.0.1:5555.
Communicate using a device
QEMU lets you use arbitrary files as disks, but the installer only populates /dev/disk when it detects disks. In practice, that means it's only useful in late_command and installer hooks that run after the directory is created. This section provides some examples for how you might use this.
First, create the file with an initial value, for example:
# Create a small file the VM can modify:
echo -n 0 > shared-device
# Or create a tarball with files you don't want to put in your `initrd`:
tar cf shared-device my-file1 my-file2 ...
Then add an option like this to your qemu command-line:
-drive file=shared-device,format=raw \
Finally, add a line to e.g. your late_command script:
# Modify the small file:
echo -n 1 > /dev/disk/by-id/ata-QEMU_HARDDISK_QM00002
# Or extract your tarball into the installed system:
tar x -C /target -f /dev/disk/by-id/ata-QEMU_HARDDISK_QM00002
The first example stores 1 in shared-device if installation succeeds, or 0 on error. The second example extracts the contents of shared-device into hd_image.qcow2.
Other possible solutions
The following could theoretically be used to communicate between host and guest, but have been found not to be useful.
virtiofs lets you mount a host directory as a filesystem in a VM. But as of bookworm, the installer uses BusyBox's mount command, which doesn't seem to support -t virtiofs.
QEMU lets you bind /dev/ttyS1 in the VM to a socket on the host, but it's limited to 11,5200 bytes per second, data needs to be base64-encoded to avoid sending special characters, buffering causes files to be truncated, and it's hard to mark end-of-file. If you want to try it, add something like this to your qemu command-line:
-chardev socket,path=installer.sock,server=on,wait=off,id=installer -serial chardev:installer
You can then communicate with the guest using e.g.:
# In the host:
socat - unix:installer.sock
# Read data into the guest:
cat /dev/ttyS1
# Write data from the guest:
cat > /dev/ttyS1
Configure partman
Preseeding uses partman to configure partitions, which can be hard to test because it runs in the middle of the installation process. Here is an easy way to test it:
use the partman-auto/expert_recipe_file setting to store your partman recipe in /partman-recipe.txt
- comment out the setting for the last question before partitioning
usually either confirming the ordinary user's password or setting up the clock
- go through the installation process until you reach that question
On the host, do: socat tcp-listen:12345 - < /path/to/your/partman-recipe.txt
- back in the VM...
use ctrl-a <space> to cycle to a shell
do: nc 10.0.2.2:12345 > /partman-recipe.txt
- this should overwrite the previous recipe
cycle back to the installer with ctrl-a <space>
- continue the installer
- when partman finishes (or fails), reload your snapshot and return to step 5
See Also
QEMU on this wiki
IRC channel: qemu
- information about specific architectures
arm64: Arm64Qemu
ppc64el: DebianInstaller/PowerPC/qemu
riscv64: RISC-V and UEFI Unified Kernel Image for Debian Installer on riscv64
- manuals