Skip to content

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:

./manage-platform.sh -h magic proxmox_vm create \
  -e vm_template_distribution=rocky9

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 qm command-line tools
  • VM template created by proxmox_template role
  • 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:

  1. If vm_template_vmid is provided → use directly
  2. Else if vm_template_name is provided → lookup template by name
  3. Else if vm_template_distribution is provided → lookup {distribution}-template
  4. 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:

  1. Query all existing VMs in range 500-8999
  2. Find highest VMID
  3. Assign highest + 1
  4. 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_state: create            # State: create or verify

VM States

create

Clones template and configures VM.

Process:

  1. Lookup Template
  2. Resolve template by VMID, name, or distribution
  3. Verify template exists
  4. Calculate VMID
  5. Use explicit VMID if provided
  6. Else find next available in range
  7. Validate not in use
  8. Generate Name
  9. Use explicit name if provided
  10. Else generate {distribution}-{YYMMDD}
  11. Clone Template
  12. Linked clone (default) or full clone
  13. Detect Boot Disk
  14. Dynamically detect boot disk name (e.g., scsi0, virtio0)
  15. Resize Disk (if needed)
  16. Get current disk size
  17. If current size < vm_disk_min_size → resize
  18. Else → keep current size
  19. Configure Cloud-init
  20. Set user and SSH key
  21. Configure network
  22. Display Summary
  23. 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:

  1. Query VM status
  2. Display VM configuration
  3. Show cloud-init settings

Example output:

VM exists: rocky9-260125 (500)
Status: stopped
Memory: 4096 MB
Cores: 4
Disk: 32G

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:

vm_linked_clone: false

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:

  1. Verify template exists:
    ssh magic.example.com "sudo qm list | grep template"
    
  2. Build template if missing:
    ./manage-platform.sh -h magic -t rocky9 proxmox_template build
    

VMID conflict

Error: VMID 500 is already in use

Solution:

  1. Use auto-assignment (omit vm_vmid)
  2. Or specify different VMID:
    -e vm_vmid=505
    

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:

  1. Verify SSH key file exists:
    ls -la ~/.ssh/id_ed25519.pub
    
  2. Check qemu-guest-agent is in template:
    ssh magic.example.com "sudo qm config 9000 | grep agent"
    
  3. Wait for cloud-init to complete (~30 seconds after boot)

Cannot delete template (linked clones exist)

Error: Cannot remove template, linked clones exist

Solution:

  1. List linked VMs:
    ssh magic.example.com "sudo qm list | grep 9000"
    
  2. Delete linked VMs first:
    ssh magic.example.com "sudo qm destroy <vmid>"
    
  3. Then delete template:
    ./manage-platform.sh -h magic -t rocky9 proxmox_template destroy
    

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:

  1. Query template configuration
  2. Extract boot disk parameter (e.g., bootdisk: scsi0)
  3. Use detected name for resize operations

SSH Key Path Expansion

SSH key paths with ~ are automatically expanded:

vm_ssh_key_file: "~/.ssh/id_ed25519.pub"
# Expands to: /home/lavender/.ssh/id_ed25519.pub

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_storage variable (e.g., local-lvm)
  • proxmox_bridge variable (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

  1. Use Auto-Features for Development
  2. Let auto-naming and auto-VMID handle uniqueness
  3. Fast iteration without manual tracking

  4. Use Explicit Configuration for Production

  5. Specify VM names, VMIDs for predictability
  6. Document VMID allocation in infrastructure-as-code

  7. Use Linked Clones for Testing

  8. Fast creation, minimal storage
  9. Easy to destroy and recreate

  10. Use Full Clones for Production

  11. Independent VMs, better performance
  12. No dependency on template

  13. Verify Before Starting

  14. Always run verify state after creation
  15. Check cloud-init configuration

  16. Track VMID Allocation

  17. Maintain VMID inventory for production
  18. 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