Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Create Debian 12 (Bookworm) KVM Guest From Cloud Image

Table of Contents

  1. About
    1. Similar Posts
  2. Steps
    1. Download The Cloud Image
    2. Install Packages
    3. Pick A Name
    4. Optional - Increase Disk Size
    5. Create Cloud Config File
    6. Create The Cloud Init ISO File
    7. Create The Guest
    8. Login
    9. Exit Console
    10. Cleanup
  3. Debugging / Tips
    1. Changing Network Settings
    2. Clones Getting Same DHCP IP
    3. Hostname And Hosts File
    4. SSH Not Working
    5. Set Timezone
  4. References
  5. Appendix
    1. Cloud Config Generator PHP Script

About

This tutorial will show you how to install Debian 12 on your KVM server through the use of one of the official Debian cloud images.

Similar Posts

Steps

Download The Cloud Image

Download the Debian 12 cloud Image:

wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2

sudo mv -i debian-12-generic-amd64.qcow2 \
  debian-12.qcow2

I really wanted to use the genericcloud image, but found that I could not get it to run cloud-init (which I needed in order to configure my user/password), so I ended up using the generic image which is the same except it includes some hardware drivers as noted here.

Install Packages

We need to ensure we have cloud-utils and whois packages for later.

sudo apt update && sudo apt install cloud-utils whois -y

The cloud-utils package is required for us to be able to run the cloud-localds
command, which allows us to create the cloud init ISO image later. It also allows us to validate our cloud-init configuration. The whois allows us to be able to run mkpasswd command later if we want to use it to generate the hashed version of our password for the cloud-init configuration. This was the command for Debian 10. Your distro may have a different name for the required packages.

Pick A Name

First, pick a name for your new VPS. I always create a "template" guest, from which I then just clone it in future to create the other guests, so I am going to call it template-debian-12.

VM_NAME="template-debian-12"

Optional - Increase Disk Size

The qcow2 image we pulled down was for a very small virtual machine that will quickly run out of space if you start using it for anything. I would recommend increasing the disk size to whatever you want for the VM. In this case I'm setting 20 GB.

DESIRED_SIZE=20G

sudo qemu-img resize \
  debian-12.qcow2 \
  $DESIRED_SIZE

Create Cloud Config File

I found lots of people had cloud-config files and it was frustrating trying to mix/match them and issues with hashing my password. I also didn't like having to be careful with making sure not to break the required YAML formatting/indentation etc. In the end I created a PHP script that I could easily tweak in order to generate my cloud-config.cfg file using some simple settings I defined at the top.

Download the script (which can also be found in the appendix).

wget https://files.programster.org/tutorials/cloud-init/create-debian-12-kvm-guest-from-cloud-image/generate.php

Fill in the settings at the top of the script as appropriate. E.g. the section outlined below:

<?php

# Fill in these settings as appropriate to you.

# Set the hostname for the guest.
$hostname = "template-debian-12";

# Specify your username and password
$username = "debian";
$password = "debian";

# Set a random salt string here for password hashing
$randomSaltString = 'setARandomStringHere';

# Specify the public keys of the private-keys you wish to login with.
$sshPublicKeys = [
    'ssh-rsa AAAAB3Nz...',
];

##### End of settings. Do not edit below this line #######
##########################################################

Then run the script to generate your cloud-init.cfg file:

php generate.php

This script relies on you having installed the YAML php extension.

I saw some example files that had autoinstall. This appears to be related to automated installation, and may be ubuntu-specific. Either way, cloud-init just ignores that key, and we don't need it at all anyway.

The users array often has 'default' in addition in examples I saw, but this results in an additional debian user that I do not need or wish to have.

You can use cloud-init schema --config-file cloud-init.cfg the validate your generated cloud init config file.

Create The Cloud Init ISO File

Create the ISO file from the cloud config file we just created:

sudo cloud-localds \
  cloud-init.iso \
  cloud-init.cfg

Create The Guest

Now you can finally run the command to create the guest:

sudo virt-install \
  --name $VM_NAME \
  --memory 1024 \
  --disk /path/to/debian-12.qcow2,device=disk,bus=virtio \
  --disk /path/to/cloud-init.iso,device=cdrom \
  --os-variant debian10 \
  --virt-type kvm \
  --graphics none \
  --network network=default,model=virtio \
  --import

The --os-variant is listed as debian10 because my Debian 10 KVM does not have debian12 in the list outputted from running sudo osinfo-query os (which I could only run after installing the libosinfo-bin package).

Login

After running the previous command, you will be taken to the login screen in the console, which you can log in with the username root. You will not be prompted for a password.

Now you really need to manually create your account username, and set a strong random password for the root user.

Exit Console

If you need to get out of the console just press ctrl + ].

Cleanup

After that, it's probably a good idea to cleanup so raw passwords aren't lying around.

sudo rm /var/lib/libvirt/images/$VM_NAME/cloud-init.iso \
  && sudo rm /var/lib/libvirt/images/$VM_NAME/cloud-init.cfg

Debugging / Tips

Changing Network Settings

One of the first things I wanted to do is change the network settings to remove the restriction to a certain static MAC address, which prevents networking working on cloned guests, as they get a new, different MAC address.. This can be done by editing the netplan file at:

/etc/netplan/50-cloud-init.yaml

Clones Getting Same DHCP IP

Debian 12 has switched to using the machine ID concept for identifying itself to your DHCP server. This means that if you create a clone and don't reset the machine ID, then both guests will identify the same, and be provided the same IP address by the DHCP server. To resolve this, reset your machine ID when you create a clone:

sudo truncate -s 0 /etc/machine-id

Then reboot. Your server will generate a new replacement machine ID on the next boot.

Hostname And Hosts File

It appears that setting the hostname in the cloud-init config will set the hostname that the machine will boot as. However, it will may not add the entry to the /etc/hosts file, so you may wish to do this quickly to prevent your machine taking unnecessarily long when performing some operations. Alternatively, you may figure out how to update the cloud-init.cfg file to update the hosts file correctly using the Etc hosts module.

SSH Not Working

If you find that openssh isn't working, you may need to run the following command to get your server to generate its host key so that you can SSH into the server. More info here.

sudo dpkg-reconfigure openssh-server

I would also suggest checking that you are happy with your /etc/ssh/sshd_config file's settings.

Set Timezone

Another thing that you may wish to do is set the timezone:

sudo dpkg-reconfigure tzdata

References

Appendix

Cloud Config Generator PHP Script

Below is a copy of the PHP script I use to create the cloud-init config file, just in case it disappears from the hosted location etc.

<?php

# Fill in these settings as appropriate to you.

# Set the hostname for the guest.
$hostname = "template-debian-12";

# Specify your username and password
$username = "debian";
$password = "debian";

# Set a random salt string here for password hashing
$randomSaltString = 'setARandomStringHere';

# Specify the public keys of the private-keys you wish to login with.
$sshPublicKeys = [
    'ssh-rsa AAAAB3Nz...',
];

##### End of settings. Do not edit below this line #######
##########################################################

# Need to hash passwords this way
$hashedPassword = crypt($password, '$6$rounds=4096$' . $randomSaltString . '$');

# Cloud-init users and groups documentation: https://bit.ly/3XFhEM0
$mainUser = [
    'name' => $username,

    # This will set the password even if the user already exists. If you don't want to allow overwriting an
    # existing user password, use 'passwd' instead
    'hashed_passwd' => $hashedPassword,

    # Allow the user to run sudo commands without a password.
    'sudo' => 'ALL=(ALL) NOPASSWD:ALL',

    # Add the user to groups if you wish
    #'groups' => [],

    'shell' => "/bin/bash",

    # Specify the lock password, which specifies whether the user can log in to the terminal with a password
    # (not to be confused with ssh_pwauth, which is for SSH connections).
    # Debian 12 likes the older lock-passwd instead of lock_passwd it appears.
    'lock-passwd' => false, # old versions are lock-passwd, new versions are lock_passwd

    'ssh_authorized_keys' => $sshPublicKeys,
];

$users = [
    $mainUser
];

$config = [
    'hostname' => $hostname,

    # Set this to true if you wish for cloud-init to set the /etc/hosts file on every boot.
    # THis would set the new hostname.
    # https://cloudinit.readthedocs.io/en/latest/reference/modules.html#update-etc-hosts
    'manage_etc_hosts' => false,

    # Allow password authentication for SSH.
    'ssh_pwauth' => false,

    # disable root login
    'disable_root' => true,

    'users' => $users,
];


$content = '#cloud-config' . PHP_EOL;

// create the yaml content, removing the optional YAML start/end dividers that nobody appears to use.
$yamlContent = yaml_emit($config);
$lines = explode(PHP_EOL, $yamlContent);
unset($lines[0]);
unset($lines[count($lines)-1]);
$content = $content . implode(PHP_EOL, $lines);

file_put_contents('cloud-init.cfg', $content);

Last updated: 1st June 2024
First published: 24th June 2023