[Proxmox Series Part 4] How to Design and Template a Standard Ubuntu/Debian VM

한국어 버전

Once you know which workloads belong in VMs, the next question is “what does a standard Linux VM look like?” With a template in place, you no longer rethink CPU types, disk buses, or Cloud-Init settings every time. This article targets Ubuntu/Debian workloads and walks through each option that new Proxmox users most often misconfigure—plus the reasoning behind every “safe default.”

How this post flows

  1. CPU type and core mapping
  2. Memory, ballooning, and HugePages defaults
  3. Disk bus, cache, and VirtIO parameters
  4. Network devices and the QEMU Guest Agent
  5. Cloud-Init plus the template workflow
  6. Common mistakes and a verification checklist

Terms introduced here

  1. x86-64-v2/v3: Virtual CPU profiles that bundle modern instruction sets.
  2. Ballooning: A feature that dynamically adjusts guest memory, useful for overcommit.
  3. VirtIO SCSI single: The default SCSI controller choice that combines good performance with TRIM support.
  4. Discard/Trim: Lets guests return unused blocks to LVM-Thin so space can be reclaimed.
  5. Cloud-Init: The initialization layer that applies users, SSH keys, and network config on first boot.

Reading card

  • Estimated time: 24 minutes
  • Prereqs: Ubuntu or Debian ISO plus a Proxmox 8.x host with ISO storage
  • After reading: you can create and reuse a standardized Linux VM template.

CPU type and core mapping

CPU type What it exposes When to pick it
kvm64 Minimal instruction set, maximum compatibility Legacy guests that refuse newer flags
x86-64-v2-AES AVX + AES, Proxmox 8 default Baseline for mixed or older hardware
x86-64-v3 Adds AVX2/VAES Only if all hosts are Zen 3 / 11th-gen Core or newer
host Copies the physical CPU flags Highest performance, zero migration safety
  1. CPU type decision: Run lscpu | grep Flags on every Proxmox host. If any node lacks avx2, stick to x86-64-v2-AES. Picking x86-64-v3 on the template and later migrating to an older CPU causes subtle segfaults. Document the chosen profile in your runbook.
  2. Core count template: Keep templates at 2 vCPUs (1 socket, 2 cores). After cloning, scale cores and sockets to match the host’s core
    ratio so NUMA pinning stays predictable. If numactl -H shows multiple NUMA nodes on the host, enable awareness with qm set <ID> --numa 1 and set CPU affinity (qm set <ID> --cpulimit 4 --cpuaffinity 0-3) so critical workloads stay on a single socket. Single-socket homelabs can skip this entirely.
  3. Changing CPU types: When you switch CPU types on an existing VM, Linux detects “new hardware,” and Windows often demands reactivation. Make the decision before running qm template so every clone inherits a known-good profile.
  4. Nested virtualization caveat: If you intend to run nested KVM/Docker, confirm the guest sees vmx/svm flags (grep vmx /proc/cpuinfo). Some CPU types mask these flags; switch to host only when you are certain migration is irrelevant.

Memory and ballooning

  1. Static allocation: Give the template 2 GB so clones boot on any host. Immediately resize per workload (e.g., nginx reverse proxy → 2 GB, PostgreSQL → 6 GB). Document both “minimum” and “burst” values.
  2. Ballooning: Only enable after verifying the guest has virtio_balloon loaded (lsmod | grep virtio_balloon) and swap sized to at least 50% of the balloon maximum (swapon --show). Without monitoring, ballooning delays OOM symptoms and confuses beginners, so the default should be off (balloon minimum = maximum). When you eventually turn it on, send alerts when balloon drops below 70% of the maximum so you notice pressure.
  3. HugePages: Leave disabled on the template. If a workload needs it, reserve huge pages on the host (GRUB default_hugepagesz=1G hugepagesz=1G hugepages=8) and set qm set <ID> --hugepages 1024. Also disable transparent huge pages (echo never > /sys/kernel/mm/transparent_hugepage/enabled) to avoid split TLB behavior.

Disk bus, cache, and VirtIO

Disk bus Pros Cons
SATA Works with anything Lower throughput, no discard
VirtIO Block Simple single-disk setup No multi-queue support
VirtIO SCSI Best performance + TRIM + multi-queue Slightly more initial clicks
  1. Controller configuration: Open Hardware → SCSI Controller and pick VirtIO SCSI (single-queue fits most homelabs; switch to multi-queue plus iothread=1 only when a VM has >4 vCPUs or sustained 100k+ IOPS). Attach every disk via SCSI, then enable Discard and SSD emulation per disk.
  2. Cache mode per storage:
    • Local SSD or Ceph RBD: Write back is safe only with a UPS or battery-backed RAID cache.
    • Local HDD or NFS: Write through avoids corruption at the cost of lower throughput.
    • Benchmark-only: Write back (unsafe) or Directsync—expect data loss if power fails, so never use them on production data.
  3. Host requirements for discard: Enabling guest-side discard only works if the host LVM-Thin pool issues discards. In the UI, open Datacenter → Storage → → Edit → Advanced and confirm Issue discard is checked. After enabling, test inside a guest:
    fallocate -l 100M /tmp/trim-test && rm /tmp/trim-test
    sudo fstrim -v /
    
    On the host, watch lvs -o lv_name,data_percent <vg> to confirm the thin pool’s data% drops. If it never decreases, discard is not working.
  4. VirtIO drivers: Ubuntu/Debian ship them by default, but keep virtio-win.iso on shared storage for the occasional Windows guest. If you later mix OSes, attach the ISO before templating so clones boot with the driver handy.

Network and QEMU Guest Agent

Network devices

  1. Bridge choice: Most labs use vmbr0 for LAN traffic. If you segment VLANs, create tagged bridges (vmbr0.10) and note the default in your template doc so future clones land on the intended network.
  2. NIC model: Pick VirtIO (paravirtualized) for every template. Set Multiqueue to min(vCPU count, 4); more queues than CPUs provide no benefit and add overhead. Validate inside the guest with ethtool -l ens18.
  3. Firewall defaults: Decide if Proxmox’s VM firewall is on by default. Templates that leave it disabled force you to remember per-clone.

QEMU Guest Agent

  1. Install inside the guest first:
    sudo apt install -y qemu-guest-agent
    sudo systemctl enable --now qemu-guest-agent
    
  2. Then enable on the host: qm set <ID> --agent enabled=1. Verify connectivity with qm agent <ID> ping. Without the running guest service, Proxmox silently falls back to ACPI power commands.
  3. What you gain: Correct IP reporting, qm shutdown reliability, and file copy via qm agent <ID> file-read. Templates missing the agent leave those features broken until you log in manually.

Cloud-Init and the template workflow

  1. Prefer official cloud images: Download the Ubuntu Cloud image (ubuntu-22.04-server-cloudimg-amd64.img), import it with qm importdisk, and attach it. They already include cloud-init and netplan hooks. If you do a manual ISO install, disable legacy netplan configs (sudo rm /etc/netplan/00-installer-config.yaml) so Cloud-Init networking takes over cleanly.
  2. Update and sanitize:
    sudo apt update && sudo apt dist-upgrade
    sudo truncate -s0 /etc/machine-id
    sudo rm -f /var/lib/dbus/machine-id
    sudo rm -rf /var/lib/cloud/instances/* /var/lib/cloud/instance
    sudo rm /etc/ssh/ssh_host_*
    sudo cloud-init clean --seed --logs --all
    
    Optionally run sudo systemd-firstboot --setup-machine-id on first boot via user-data so each clone gets a unique ID.
  3. Attach the Cloud-Init drive: Hardware → Add → Cloud-Init Drive. Always prefer shared storage (cephfs, nfs-templates, shared LVM-Thin): if the drive lives on local-lvm, qm migrate fails because the new node cannot see the ISO. Only single-node homelabs that accept this risk should use local storage. Inside the guest, confirm cloud-init query --format '%(datasource_type)s' returns ConfigDrive; if it does not, check that /dev/sr0 exists and the drive is mounted.
  4. Convert to a template:
    qm shutdown <ID>
    qm template <ID>
    
    Remember: template disks become read-only. Clone the template if you ever need to edit it later.
  5. Clone and customize:
    qm clone <TEMPLATE-ID> <NEW-ID> --name web-01
    qm set <NEW-ID> --ipconfig0 "ip=192.168.10.20/24,gw=192.168.10.1"
    
    Add SSH keys and users via the Cloud-Init tab or a user-data snippet rather than the CLI, which logs arguments. After filling hostname, DNS, user, and keys, boot the VM and verify cloud-init status --wait, hostnamectl, ip addr, and /var/log/cloud-init-output.log to confirm successful customization. If cloud-init status hangs, rerun with --timeout 15 to avoid locking the validation step.

Common mistakes and checklist

  • CPU type: Setting the template to host pins you to the current hardware. Verify the oldest host before you pick x86-64-v3.
  • Ballooning: Enabling without swap or monitoring masks memory pressure, then everything OOMs at once. Keep it off until you measure workloads.
  • Guest Agent: Installing on some clones but not others means qm shutdown behaves inconsistently. Bake it into the template instead.
  • Cloud-Init drive: Forgetting it forces you to hand-edit /etc/netplan every time. Attach before templating.
  • Discard: Checking the box but skipping host-side discard or fstrim lets the thin pool fill silently.

Checklist (tick every time you cut a template):

  • Chosen CPU type documented and validated on all hosts
  • Template memory size, ballooning policy, and NUMA flag recorded
  • Disk controller, cache mode, discard, and fstrim schedule noted
  • vmbr/VLAN choice, NIC queues, firewall default, and Guest Agent install script included
  • Cloud-Init drive lives on the correct storage; machine-id, SSH keys, and cloud caches cleared
  • Post-clone validation steps (cloud-init status, hostnamectl, new SSH fingerprints) performed

Wrap-up

A standardized Linux VM accelerates every future workload and keeps multiple operators aligned. Once CPU, memory, disk, and network defaults are in place, each clone only needs workload-specific tweaks. Templates also reduce the chance of forgetting VirtIO drivers or Cloud-Init settings.

Next, we will connect these VM and LXC setups to a backup-and-restore routine so they remain recoverable.

💬 댓글

이 글에 대한 의견을 남겨주세요