Category: Security

  • VM Provisioning With Proxmox

    Virtualization

    VM Provisioning With Proxmox

    May 15, 2026~13 min readProxmox . templates . KVM

    Why I template everything

    Spinning up a fresh VM from scratch every time — attaching the ISO, clicking through the installer, setting the timezone, patching, installing the guest agent — is fine once. Doing it for the tenth machine is a waste of an afternoon. The fix is to do that work exactly once: get a single VM into a clean, fully updated, known-good state, and then turn it into a template. After that, every new machine is a clone that comes up in seconds, already patched and configured.

    This is the workflow I actually use in my lab: build one Debian VM the normal way, get it stable and current, generalize it so the clones are not carbon copies, convert it to a template, and clone from it whenever I need a new box.

    01 Create VM qm create + ISO 02 Install OS from ISO 03 Update patch + agent 04 Generalize clean identity 05 Template qm template 06 Clone qm clone

    Prerequisites

    You will need:

    • A working Proxmox VE node (this lab runs 8.x), with enough storage for a base disk plus clones.
    • An OS ISO uploaded to the node. In the web UI that is Datacenter > your storage > ISO Images > Upload; on disk it lands in /var/lib/vz/template/iso/. This guide uses Debian 12, but the same steps apply to almost any Linux.
    • Access to the node — either the web UI, or SSH / the node shell as root for the command-line steps.
    Note

    Pick a VMID convention and stick to it. I reserve 9000 for the template and use the 1xx range for real machines, so it is obvious at a glance which VM is the golden image and which are clones.

    Step 1 – Create the VM

    Create a normal VM that you will install an OS into. In the web UI, click Create VM in the top right and walk through the wizard; the settings that matter are below. If you prefer the shell, the equivalent qm commands follow.

    Wizard tabs worth setting deliberately:

    • General — set the VMID to 9000 and a clear name such as debian12-base.
    • OS — select the ISO you uploaded.
    • System — tick the QEMU Guest Agent box (you will install the agent inside the guest later), and leave machine type and BIOS at their defaults unless you need UEFI.
    • Disks — a 16-32 GB disk on your main storage (for example local-lvm) is plenty for a base image; use the VirtIO SCSI controller and tick Discard if your storage is thin-provisioned.
    • CPU — 2 cores, type host for best performance.
    • Memory — 2048 MB is fine for a base Linux.
    • Network — the VirtIO model on your management bridge (for example vmbr0).
    # Create the VM shell (CPU, memory, NIC, guest agent enabled)
    qm create 9000 --name debian12-base --memory 2048 --cores 2 --cpu host \
        --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci --agent enabled=1
    
    # Add a 32 GB disk on local-lvm
    qm set 9000 --scsi0 local-lvm:32
    
    # Attach the installer ISO and set the boot order
    qm set 9000 --ide2 local:iso/debian-12.7.0-amd64-netinst.iso,media=cdrom
    qm set 9000 --boot 'order=scsi0;ide2'

    Substitute the bridge, storage and ISO filename that match your node.

    Step 2 – Install the guest OS

    Start the VM and open the Console from the Proxmox UI, then run through the OS installer the way you normally would:

    • Choose your language, keyboard and timezone.
    • Set a hostname — something generic like debian-base; clones will get their real names later.
    • Partition the disk (the guided whole-disk option is fine for a template).
    • Create your admin user, and if the installer offers it, select the SSH server and standard system utilities.

    When the installer finishes, let the VM reboot and log in at the console. Detach the ISO once it is booted so the template is not tied to an installer image — in the UI, Hardware > CD/DVD Drive > Edit > Do not use any media, or from the shell:

    qm set 9000 --ide2 none

    Step 3 – Update and stabilize

    This is the part that makes templating worth it: get the machine to a clean, current baseline so every future clone starts there. Inside the guest, fully update the system and install the QEMU guest agent (it is what lets Proxmox report the VM IP, do clean shutdowns, and run guest commands):

    # Update the package index and upgrade everything
    sudo apt update && sudo apt full-upgrade -y
    
    # Install the QEMU guest agent (and any tools you always want present)
    sudo apt install -y qemu-guest-agent
    
    # Enable the agent so it starts on every boot
    sudo systemctl enable --now qemu-guest-agent

    Now is also the time to bake in anything you want on every machine — your shell config, a monitoring agent, common packages, sane sshd settings. The whole point is that you do this once. When you are happy, reboot and confirm everything comes back cleanly:

    sudo reboot
    Tip

    The guest agent only fully works once it is enabled on the VM as well (the QEMU Guest Agent box from Step 1, also under Options). After a reboot the Summary tab should start showing the VM IP address, which confirms the agent is talking to the host.

    Step 4 – Generalize before templating

    If you clone a VM as-is, every clone inherits the same machine identity — the same machine-id, the same SSH host keys, and often the same DHCP behavior — which causes subtle, annoying problems on a network. Before you convert to a template, strip the machine-specific bits so each clone regenerates its own. Run these in the guest as the final thing before shutting down:

    # Clear the unique machine-id (it regenerates on next boot)
    sudo truncate -s 0 /etc/machine-id
    sudo rm -f /var/lib/dbus/machine-id
    sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
    
    # Remove SSH host keys so each clone generates its own
    sudo rm -f /etc/ssh/ssh_host_*
    
    # Clear logs and shell history for a clean image
    sudo truncate -s 0 /var/log/*log 2>/dev/null
    cat /dev/null > ~/.bash_history && history -c

    Then shut the VM down — do not boot it again, or it will regenerate the very files you just cleared:

    sudo shutdown now
    Why this matters

    Duplicate SSH host keys make clients warn about possible man-in-the-middle attacks, and duplicate machine-ids confuse anything that keys off them (DHCP reservations, logging, some agents). Clearing them here means the first boot of every clone produces a fresh, unique identity.

    Step 5 – Convert to a template

    With the VM shut down and generalized, convert it. In the web UI, right-click VM 9000 and choose Convert to template. From the shell it is one command:

    qm template 9000

    Once converted, the VM icon changes and the machine becomes read-only — you can no longer start it directly, only clone from it. That is your golden image.

    Keeping it current

    Templates go stale as patches pile up. Every month or two I clone the template to a temporary VM, boot it, run the update and generalize steps again, re-template it, and delete the temporary clone. It keeps new machines from needing a big patch run the moment they come up.

    Step 6 – Clone and deploy

    Now the payoff. To stand up a real machine, clone the template. In the UI, right-click the template and choose Clone, then pick a new VMID and name and choose the clone mode. From the shell:

    # Full clone into VMID 120 -- an independent copy of the disk
    qm clone 9000 120 --name ad-dc01 --full
    
    # Or a linked clone -- fast and space-saving, but tied to the template
    qm clone 9000 121 --name test01

    A full clone is a completely independent VM; a linked clone shares the template base disk, so it is near-instant and uses almost no extra space but cannot outlive the template. I use full clones for anything that matters and linked clones for throwaway testing.

    Start the clone, open the console, and give it its own identity:

    # Set a unique hostname
    sudo hostnamectl set-hostname ad-dc01
    
    # Regenerate SSH host keys (skip if your first-boot tooling handles it)
    sudo dpkg-reconfigure openssh-server
    
    # If you use static IPs, edit the interfaces file / netplan, then reboot
    sudo reboot

    Because you cleared the machine-id and host keys before templating, the clone comes up with a fresh identity on first boot. Confirm the guest agent is reporting and the network is correct from the host:

    qm guest cmd 120 network-get-interfaces

    Automating it further

    Once the template exists, I rarely clone by hand for anything repeatable. A short script wraps the clone-and-customize steps so a new machine is one command:

    #!/usr/bin/env bash
    # new-vm.sh <newid> <name>  -- clone the template and start it
    set -euo pipefail
    TEMPLATE=9000
    NEWID="$1"
    NAME="$2"
    qm clone "$TEMPLATE" "$NEWID" --name "$NAME" --full
    qm start "$NEWID"
    echo "Cloned $TEMPLATE -> $NEWID ($NAME) and started it"

    From there it is easy to layer on whatever configuration management you use — Ansible against the new IP, for instance — but even the bare clone gets you a patched, ready machine in seconds instead of a fresh install every time.

    Wrap-up

    That is the whole loop: build one VM properly, update it, generalize it, and template it once — then clone forever. The up-front half hour of getting a single machine right pays for itself the moment you need your second, third, or tenth box. From here, clone whatever the lab needs: domain controllers, a GitLab runner, a SIEM collector, or a scratch VM you will delete in an hour.