Proxmox VM Role¶
Create and manage Proxmox VMs from templates with intelligent automation and smart defaults.
Purpose¶
The proxmox_vm role handles VM lifecycle operations with advanced features:
- Smart Template Selection: Priority-based lookup (VMID > name > distribution)
- Auto-naming: Generates unique VM names with date stamps
- Auto-VMID Assignment: Finds next available VMID in configurable range
- Minimum Disk Sizing: Ensures minimum size without shrinking template disks
- Linked Clones: Fast, space-efficient cloning (default)
- Cloud-init Integration: SSH keys, users, networking pre-configured
Quick Start¶
Minimal Configuration¶
Everything auto-generated:
Result:
- Template: rocky9-template (auto-discovered)
- VM name: rocky9-260125 (auto-generated with today's date)
- VMID: 500 (next available in range)
- Disk: Template size (32G, no resize needed)
Explicit Configuration¶
Full control over all settings:
./manage-platform.sh -h magic proxmox_vm create \
-e vm_template_distribution=rocky10 \
-e vm_name="prod-web-01" \
-e vm_vmid=600 \
-e vm_disk_min_size="100G" \
-e vm_memory=16384 \
-e vm_cores=8
Requirements¶
- Proxmox VE 8.x host with
qmcommand-line tools - VM template created by
proxmox_templaterole - SSH access to Proxmox host with sudo privileges
- Cloud-init enabled in template
- SSH public key for cloud-init injection
Role Variables¶
Template Selection (Pick One)¶
Priority: VMID > Name > Distribution
# Option 1: By distribution (most common)
vm_template_distribution: rocky9 # Looks up "rocky9-template"
# Option 2: By template name
vm_template_name: "rocky9-custom-template"
# Option 3: By explicit VMID (highest priority)
vm_template_vmid: 9000
How it works:
- If
vm_template_vmidis provided → use directly - Else if
vm_template_nameis provided → lookup template by name - Else if
vm_template_distributionis provided → lookup{distribution}-template - Else → fail (no template specified)
Template existence is verified before cloning.
VM Identification¶
# VM name (auto-generated if empty)
vm_name: "" # Default: {distribution}-{YYMMDD}
# VMID (auto-assigned if empty)
vm_vmid: "" # Default: next free in range
vm_vmid_base: 500 # Auto-assign range start
vm_vmid_max: 8999 # Auto-assign range end
Auto-naming Examples:
| Distribution | Date | Generated Name |
|---|---|---|
| rocky9 | 2026-01-25 | rocky9-260125 |
| debian12 | 2026-01-25 | debian12-260125 |
| rocky10 | 2026-02-15 | rocky10-260215 |
Auto-VMID Logic:
- Query all existing VMs in range 500-8999
- Find highest VMID
- Assign
highest + 1 - Validate not in use (fail if conflict)
VM Resources¶
vm_memory: 4096 # RAM in MB
vm_cores: 4 # CPU cores
vm_disk_min_size: "20G" # Minimum boot disk size (won't shrink)
vm_linked_clone: true # Use linked clone (faster, less space)
Disk Sizing Logic:
| Template Size | vm_disk_min_size |
Result | Action |
|---|---|---|---|
| 32G | 20G | 32G | No resize (already larger) |
| 32G | 50G | 50G | Resize to 50G |
| 10G | 20G | 20G | Resize to 20G |
The role ensures the disk is at least the specified size but won't shrink existing template disks.
Cloud-init Configuration¶
vm_ansible_user: lavender # SSH user for cloud-init
vm_ssh_key_file: "~/.ssh/id_ed25519.pub" # SSH key to inject
vm_ip_config: "ip=dhcp" # Network config
Cloud-init features:
- Creates user account
- Injects SSH public key (passwordless auth)
- Configures network (DHCP or static)
- Sets hostname
VM State¶
VM States¶
create¶
Clones template and configures VM.
Process:
- Lookup Template
- Resolve template by VMID, name, or distribution
- Verify template exists
- Calculate VMID
- Use explicit VMID if provided
- Else find next available in range
- Validate not in use
- Generate Name
- Use explicit name if provided
- Else generate
{distribution}-{YYMMDD} - Clone Template
- Linked clone (default) or full clone
- Detect Boot Disk
- Dynamically detect boot disk name (e.g.,
scsi0,virtio0) - Resize Disk (if needed)
- Get current disk size
- If current size <
vm_disk_min_size→ resize - Else → keep current size
- Configure Cloud-init
- Set user and SSH key
- Configure network
- Display Summary
- Show VM details (name, VMID, template, disk)
Example output:
VM created successfully!
Name: rocky9-260125
VMID: 500
Template: rocky9-template (9000)
Disk: 32G (linked clone)
Cloud-init: Configured with SSH key
verify¶
Checks VM exists and displays configuration.
Process:
- Query VM status
- Display VM configuration
- Show cloud-init settings
Example output:
Future States (Phase 2)¶
Planned VM lifecycle operations:
- start: Boot VM
- stop: Stop VM (non-graceful)
- shutdown: Graceful shutdown
- remove: Destroy VM
- modify: Change VM properties (CPU, RAM, disk)
Example Playbooks¶
Minimal Configuration¶
---
- name: Create Rocky 9 VM
hosts: magic
become: true
roles:
- jackaltx.solti_platforms.proxmox_vm
vars:
vm_template_distribution: rocky9
Result: VM rocky9-260125 created at next available VMID.
Explicit Configuration¶
---
- name: Create Production Server
hosts: magic
become: true
roles:
- jackaltx.solti_platforms.proxmox_vm
vars:
vm_template_distribution: rocky10
vm_name: "prod-web-01"
vm_vmid: 600
vm_disk_min_size: "100G"
vm_memory: 16384
vm_cores: 8
Full Clone¶
---
- name: Create Independent VM
hosts: magic
become: true
roles:
- jackaltx.solti_platforms.proxmox_vm
vars:
vm_template_distribution: debian12
vm_name: "standalone-db"
vm_linked_clone: false
Custom Cloud-init¶
---
- name: Create VM with Custom User
hosts: magic
become: true
roles:
- jackaltx.solti_platforms.proxmox_vm
vars:
vm_template_distribution: rocky9
vm_ansible_user: admin
vm_ssh_key_file: "~/.ssh/admin_key.pub"
vm_ip_config: "ip=10.0.0.50/24,gw=10.0.0.1"
Usage Examples¶
Using Management Scripts¶
# Create VM with auto-generated name and VMID
./manage-platform.sh -h magic proxmox_vm create \
-e vm_template_distribution=rocky9
# Create VM with explicit name
./manage-platform.sh -h magic proxmox_vm create \
-e vm_template_distribution=rocky9 \
-e vm_name="my-server"
# Create VM with custom resources
./manage-platform.sh -h magic proxmox_vm create \
-e vm_template_distribution=debian12 \
-e vm_vmid=505 \
-e vm_memory=8192 \
-e vm_cores=8 \
-e vm_disk_min_size="50G"
# Verify VM
./platform-exec.sh -h magic proxmox_vm verify -e vm_vmid=500
Direct Playbook Usage¶
# Create VM
ansible-playbook -i inventory.yml playbooks/create-vm.yml \
-e vm_template_distribution=rocky9 \
-e vm_name="test-vm"
# Verify VM
ansible-playbook -i inventory.yml playbooks/verify-vm.yml \
-e vm_vmid=500
Testing Workflow¶
Complete test workflow:
# 1. Create test VM
./manage-platform.sh -h magic proxmox_vm create \
-e vm_template_distribution=rocky9 \
-e vm_name="test-vm"
# 2. Verify creation
./platform-exec.sh -h magic proxmox_vm verify -e vm_vmid=500
# 3. Start VM
ssh magic.example.com "sudo qm start 500"
# 4. Wait for cloud-init (30 seconds)
sleep 30
# 5. Get IP address
ssh magic.example.com "sudo qm guest cmd 500 network-get-interfaces"
# 6. SSH into VM (using cloud-init configured key)
ssh lavender@<vm-ip>
# 7. Cleanup
ssh magic.example.com "sudo qm stop 500"
ssh magic.example.com "sudo qm destroy 500"
Cloning Strategies¶
Linked Clone (Default)¶
Advantages:
- Fast creation (seconds)
- Space-efficient (only deltas stored)
- Consistent base across VMs
Disadvantages:
- Requires template to exist
- Cannot delete template while linked VMs exist
- Slightly slower disk I/O
When to use: Development, testing, ephemeral environments
Full Clone¶
Advantages:
- Fully independent VM
- Can delete template
- Better disk I/O performance
Disadvantages:
- Slower creation (~30-60 seconds)
- Uses more storage space
- Disk divergence across VMs
When to use: Production, long-lived VMs, template will be deleted
Enable full clone:
VMID Allocation Strategy¶
| VMID Range | Purpose | Assignment |
|---|---|---|
| 9000-9999 | Templates | Auto-calculated by proxmox_template |
| 500-8999 | VMs | Auto-assigned by proxmox_vm (default) |
| 100-499 | VMs | Manual assignment (user-specified) |
| 1-99 | Reserved | Proxmox system use |
Auto-assignment example:
# First VM created
./manage-platform.sh -h magic proxmox_vm create -e vm_template_distribution=rocky9
# Result: VMID 500
# Second VM created
./manage-platform.sh -h magic proxmox_vm create -e vm_template_distribution=rocky9
# Result: VMID 501
# Third VM created with explicit VMID
./manage-platform.sh -h magic proxmox_vm create \
-e vm_template_distribution=rocky9 \
-e vm_vmid=700
# Result: VMID 700
# Fourth VM created
./manage-platform.sh -h magic proxmox_vm create -e vm_template_distribution=rocky9
# Result: VMID 701 (next after highest: 700+1)
Troubleshooting¶
Template not found¶
Error: Template rocky9-template does not exist
Solution:
- Verify template exists:
- Build template if missing:
VMID conflict¶
Error: VMID 500 is already in use
Solution:
- Use auto-assignment (omit
vm_vmid) - Or specify different VMID:
Disk resize fails¶
Error: Cannot resize disk
Cause: Attempting to shrink disk
Solution: Ensure vm_disk_min_size >= template disk size
Cloud-init not working¶
Symptoms: Cannot SSH into VM, no user created
Diagnosis:
# Check cloud-init configuration
ssh magic.example.com "sudo qm cloudinit dump 500 user"
# Verify SSH key
ssh magic.example.com "sudo qm cloudinit dump 500 user" | grep ssh_authorized_keys
Solutions:
- Verify SSH key file exists:
- Check qemu-guest-agent is in template:
- Wait for cloud-init to complete (~30 seconds after boot)
Cannot delete template (linked clones exist)¶
Error: Cannot remove template, linked clones exist
Solution:
- List linked VMs:
- Delete linked VMs first:
- Then delete template:
Advanced Features¶
Dynamic Boot Disk Detection¶
The role automatically detects the boot disk name (e.g., scsi0, virtio0, sata0) from the template configuration. This allows support for different disk controller types without hardcoding.
How it works:
- Query template configuration
- Extract boot disk parameter (e.g.,
bootdisk: scsi0) - Use detected name for resize operations
SSH Key Path Expansion¶
SSH key paths with ~ are automatically expanded:
Validation Checks¶
The role performs validation before operations:
- Template exists (fail early if missing)
- VMID not in use (prevent conflicts)
- SSH key file exists (fail before VM creation)
- Required variables provided (distribution or VMID or name)
Dependencies¶
- proxmox_template role (to create templates)
- Proxmox host inventory with:
proxmox_storagevariable (e.g.,local-lvm)proxmox_bridgevariable (e.g.,vmbr0)
Files Created¶
On Proxmox Host¶
- VM instance at specified VMID
- Boot disk on
{{ proxmox_storage }} - Cloud-init drive on
{{ proxmox_storage }}
Metadata¶
VM configuration includes:
- Template reference (for linked clones)
- Cloud-init configuration
- Hardware settings (CPU, RAM)
Integration Examples¶
With CI/CD¶
# .gitlab-ci.yml or similar
test:
script:
# Create test VM
- ./manage-platform.sh -h magic proxmox_vm create -e vm_template_distribution=rocky9
# Start VM
- ssh magic "sudo qm start 500"
# Wait for boot
- sleep 30
# Run tests
- ansible-playbook -i test-inventory.yml test-suite.yml
# Cleanup
- ssh magic "sudo qm destroy 500"
With Other Collections¶
---
- name: Deploy Application Stack
hosts: localhost
tasks:
# Create database VM
- include_role:
name: jackaltx.solti_platforms.proxmox_vm
vars:
vm_template_distribution: debian12
vm_name: "app-db"
vm_memory: 8192
# Create web VM
- include_role:
name: jackaltx.solti_platforms.proxmox_vm
vars:
vm_template_distribution: rocky10
vm_name: "app-web"
vm_cores: 4
# Deploy monitoring (solti-monitoring)
- include_role:
name: jackaltx.solti_monitoring.alloy
vars:
target_hosts: app-db,app-web
Performance Considerations¶
Linked vs Full Clone Speed¶
| Operation | Linked Clone | Full Clone |
|---|---|---|
| Creation time | 5-10 seconds | 30-60 seconds |
| Disk space | ~1-2 GB (deltas) | Full template size (32GB+) |
| I/O performance | Slight overhead | Native speed |
Resource Allocation¶
Recommendations:
- Development: 2-4 cores, 2-4 GB RAM, linked clones
- Testing: 4 cores, 4-8 GB RAM, linked clones
- Production: 4+ cores, 8+ GB RAM, full clones
Batch VM Creation¶
Create multiple VMs efficiently:
# Sequential (safe, slower)
for dist in rocky9 rocky10 debian12; do
./manage-platform.sh -h magic proxmox_vm create \
-e vm_template_distribution=$dist
done
# Parallel (faster, requires careful VMID management)
./manage-platform.sh -h magic proxmox_vm create -e vm_template_distribution=rocky9 -e vm_vmid=500 &
./manage-platform.sh -h magic proxmox_vm create -e vm_template_distribution=rocky10 -e vm_vmid=501 &
./manage-platform.sh -h magic proxmox_vm create -e vm_template_distribution=debian12 -e vm_vmid=502 &
wait
Best Practices¶
- Use Auto-Features for Development
- Let auto-naming and auto-VMID handle uniqueness
-
Fast iteration without manual tracking
-
Use Explicit Configuration for Production
- Specify VM names, VMIDs for predictability
-
Document VMID allocation in infrastructure-as-code
-
Use Linked Clones for Testing
- Fast creation, minimal storage
-
Easy to destroy and recreate
-
Use Full Clones for Production
- Independent VMs, better performance
-
No dependency on template
-
Verify Before Starting
- Always run
verifystate after creation -
Check cloud-init configuration
-
Track VMID Allocation
- Maintain VMID inventory for production
- Use ranges (e.g., 600-699 for web servers, 700-799 for databases)
License¶
MIT-0
Author Information¶
Part of the Solti Ansible Collections suite - https://github.com/jackaltx/solti-platforms