Using virt-install to create unattended virtual machine install


5 min read


Virt-manager is a great tool to replace VirtualBox or VMware Workstation on Linux, when GNOME boxes is not enough.

I use virtualization on a daily basis and often need to set-up new virtual machines (VM). Virt-manager has a wizard to create new VM, where you specify the ISO, select all you need, but then we have to go through the installation process.

All that takes time, so to speed up, instead of going through the GUI, we'll use virt-install(install virt-install package if needed) to create an unattended installation of a debian server with just a serial console.

Unattended VM creation

We first need to create a password file for root and one for the user. They are in plaintext, and the password must be on the first line:

echo "toor" > root-pwd.txt
echo "resu" > user-pwd.txt

Now let's do the installation:

virt-install --install debian12 \
    --graphics none \
    --console pty,target_type=serial \
    --noautoconsole \
    --extra-args="console=ttyS0,115200n8" \
    --unattended profile=jeos,admin-password-file=root-pwd.txt,user-login=user,user-password-file=user-pwd.txt

Let's go through the options:

  • --install debian12: Use debian 12. To list all the possible options, use osinfo-query os. What we need is the shortcode.

  • --graphics none: No graphics (We can add later on) but since this is a server, we'll just use SSH

  • --console pty,target_type=serial: Add a PTY console to the virtual machine

  • --noautoconsole: Don't automatically try to connect to the guest console

  • --extra-args="console=ttyS0,115200n8": Arguments when starting the kernel to output to the serial console

  • --unattended profile=jeos,admin-password-file=root-pwd.txt,user-login=user,user-password-file=user-pwd.txt: Make an unattended installation. If not specified, we'll have to install ourselves.

    • profile=jeos: Use the JeOS profile (instead of default desktop)

    • admin-password-file=password.txt: Use the password in the password.txt file we created just before for root.

    • user-login=user,user-password-file=user-pwd.txt: Create a user named userwith the password provided in the file. By default OpenSSH doesn't allow root to connect, so if we don't have a user, we won't be able to connect via SSH.

It will output something similar to:

Using default --name debian12
Using debian12 default --memory 2048
Using debian12 default --disk size=20

Starting install...
Retrieving 'linux'                                          | 7.6 MB  00:14 ... 
Retrieving 'initrd.gz'                                      |  39 MB  01:05 ... 
Allocating 'virtinst-pm9z6mm7-linux'                        |    0 B  00:00 ... 
Transferring 'virtinst-pm9z6mm7-linux'                      |    0 B  00:00 ... 
Allocating 'virtinst-c_yhjtv8-initrd.gz'                    |    0 B  00:00 ... 
Transferring 'virtinst-c_yhjtv8-initrd.gz'                  |    0 B  00:00 ... 
Allocating 'debian12.qcow2'                                 |    0 B  00:00 ... 
Creating domain...                                          |    0 B  00:00     

Domain is still running. Installation may be in progress.
You can reconnect to the console to complete the installation process.

We haven't specified the name of the VM (--name), the network (--network) to use, the location for the disk or its size (--disk), as well the RAM (--memory).

It defaulted to debian12 for the name. For the RAM and disk size, it uses defaults from its database. As for the network, it will use the default adapter, and it will store the disk in the defaultstorage pool, which is in /var/lib/libvirt/images.

It starts by downloading the necessary files to start the installation, then runs it in the background as requested.

Monitoring installation progress

While not necessary, we can look at the console to see the progress:

sudo virsh console debian12

We can also continue with virt-manager instead.

It should complete in a few minutes, then it will poweroff the system.

It's a basic server with SSH, so we'll still need to adjust a few things such as setting the timezone, adding users, and providing SSH key for authentication.

Accessing the system via SSH

It's useful to have console access, but SSH is more practical.

List running domains

We can list the running virtual machines (aka domains) with sudo virsh list:

user@fedora:~$ sudo virsh list
 Id   Name      State
 1    dev       running
 2    freebsd   running
 3    win11     running
 4    spotify   running

debian12 is not present in the list, so it must be in another state. We can add the --all parameter to the command to list all the VM in any state:

user@ubuntu:~$ sudo virsh list --all
 Id   Name                State
 1    dev                 running
 2    freebsd             running
 3    win11               running
 4    spotify             running
 -    AI                  shut off
 -    debian12            shut off
 -    Kali                shut off
 -    storage             shut off

It's shut off at this time.

Start the domain

user@ubuntu:~$ sudo virsh start debian12
Domain 'debian12' started

Let's give it a few seconds to boot so that it can get an IP address.

Obtaining VM IP address

Our VM got attached to the default network. We'll query the DHCP leases to figure out what IP address it got:

 Expiry Time           MAC address         Protocol   IP address           Hostname   Client ID or DUID
 2024-05-25 12:06:08   52:54:00:de:18:bb   ipv4   -          01:52:54:00:12:18:aa
 2024-05-25 12:25:07   52:54:00:de:98:bb   ipv4   debian12   ff:00:12:18:aa:00:01:00:01:2d:e4:67:8b:52:54:00:12:18:aa
 2024-05-25 12:19:29   52:54:00:a8:63:74   ipv4   pc         01:52:54:00:a8:68:75
 2024-05-25 12:16:25   52:54:00:c4:28:fe   ipv4    -          01:52:54:00:b5:28:ce
 2024-05-25 12:16:56   52:54:00:32:05:09   ipv4   -          01:52:54:00:c2:83:63

We can easily identify our debian12, it got Now we're ready to SSH into it.

A few things

The big gotcha is that not all distributions are supported. We can list the ones that support the --unattended option using the following script (on Ubuntu, we need the osinfo-db-tools package):


echo "Checking install scripts for each OS..."

# List all OSes and their short IDs
os_list=$(osinfo-query os)

# Loop through each OS and check if it has an install script
while IFS= read -r line; do
    # Extract the short ID and the full name of the OS
    short_id=$(echo "$line" | awk '{print $1}')
    name=$(echo "$line" | awk -F '|' '{print $2}')

    # Check if the OS has an install script
    if osinfo-install-script "$short_id" &> /dev/null; then
        echo "$name ($short_id) has an install script."
done <<< "$os_list"

The database (containing the unattended installation scripts, URL locations, etc) gets updated once in a while, but we can also update it manually. Using the nightly option to get the most recent development version (--latest is a lesser adventurous option), it looks like this:

osinfo-db-import --nightly

A bit more than a month after the release of Ubuntu 24.04 and Fedora 40, only Fedora 40 support was added.

Furthermore, while a number of distributions have unattended installation, they don't always have an install tree URL. As an example, for Ubuntu, we have to go back all the way to 20.04 for the install tree URL to be in the database. Anything more recent than 20.04 doesn't have one available. However, if we have an install tree and provide its path (--location), it should be able to do the unattended installation.