VM Provisioning Patterns
VM Existence Check Requirements
Each provision role MUST check for existing VM/container before creating. Guard ALL creation tasks with
when: not vm_exists | bool(VMs) orwhen: not lxc_exists | bool(containers viaproxmox_lxc).Standard VM existence check pattern:
- name: Check if VM already exists ansible.builtin.command: cmd: qm status {{ vm_id }} register: vm_status failed_when: false changed_when: false - name: Set VM existence flag ansible.builtin.set_fact: vm_exists: "{{ vm_status.rc == 0 }}"
VM Startup Configuration
Every VM MUST configure
--onboot 1 --startup order=Nviaqm set. This task runs unconditionally to self-heal. Define<type>_vm_startup_orderin role defaults.Auto-start configuration pattern:
- name: Configure VM to start on boot ansible.builtin.command: cmd: >- qm set {{ vm_id }} --onboot 1 --startup order={{ vm_startup_order }}
Step-by-Step: Adding a New VM Type
- Complete process for creating a new VM service (using
homeassistantas example):
Create provision role:
roles/homeassistant_vm/
├── defaults/main.yml
├── meta/main.yml
└── tasks/main.yml
Create configure role:
roles/homeassistant_configure/
├── defaults/main.yml
├── meta/main.yml
└── tasks/main.yml
Add VMID to group_vars:
# inventory/group_vars/all.yml
homeassistant_vm_id: 200
homeassistant_vm_name: homeassistant
homeassistant_vm_memory: 2048
homeassistant_vm_cores: 2
homeassistant_vm_disk_size: 32G
homeassistant_image_path: images/haos.qcow2
Add to inventory and site.yml, update Molecule, create VM-specific skill.
Add Host Pattern
- Dynamic inventory pattern:
- name: Add VM to dynamic inventory ansible.builtin.add_host: name: "{{ vm_name }}" groups: dynamic_group ansible_host: "<bootstrap_ip>"
Design Principles
Bake, don't configure at runtime: Custom images are REQUIRED. Provision roles verify the image exists and hard-fail if missing. Configure roles NEVER install packages.
One path, no fallbacks: NEVER add stock/generic image fallback logic. One tested code path per feature. Missing prerequisites fail with an actionable error message.
Follow community standards: Check upstream tooling before writing custom workarounds.
Documented Exceptions
Three documented exceptions to bake principle (each MUST be explicitly documented):
- Docker pull of pinned image tag: deterministic and versioned
- Desktop LXC via build-images.sh: rootfs tarball with all packages baked in
- Windows VMs via ISO + autounattend.xml: install-from-ISO IS the bake approach for Windows
Any OTHER runtime package installation is rejected. If you need a new package, add it to the image build script.
Per-Host VMID Collision Avoidance
When multiple hosts share a Proxmox node (test environments), a static VMID causes conflicts. Compute per-host VMID as
base_id + groups['flavor_group'].index(inventory_hostname). Apply the SAME computation in provision, verify, and cleanup.Previous bug: VMID 600 was used for both
homeandmesh1on the same physical Proxmox host. The secondqm createfailed with "Configuration file already exists."
Upload Path Selection
NEVER upload large images (>5 GB) to
/tmp/on Proxmox — it's typically tmpfs with limited size (~7.8 GB). Use/var/tmp/which lives on the root filesystem. This applies to both image uploads andqemu-img convertoutput.Previous bug:
qemu-img convertof an 18 GB Windows qcow2 to/tmp/failed with "No space left on device".