Finally Got the Automated Ansible VM Deployment Working
Converting a Base Ubuntu Image
Before creating a VM template, we needed a clean Ubuntu base image. Proxmox works best with raw disk formats, so we had to convert a QCOW2 image to RAW:
qemu-img convert -O raw /var/lib/vz/template/iso/ubuntu.qcow2 /var/lib/vz/template/iso/ubuntu.raw
This ensures that Proxmox can efficiently use the image for cloning operations.
Creating a Proxmox VM Template
Next, we created a base VM that would act as our Cloud-Init template. This VM was manually configured to have essential settings such as CPU, memory, disk, and networking:
qm create 3000 --name auto-test --sockets 1 --cores 4 --numa 0 --memory 4096 \
--net0 virtio,bridge=cormgt_out,macaddr=cc:44:8a:aa:1a:50 --scsihw virtio-scsi-pci --machine q35
qm set 3000 --scsi0 mcp-zfs1:vm-3000-disk-0
qm set 3000 --ide2 local-lvm:cloudinit
qm set 3000 --boot c --bootdisk scsi0
qm set 3000 --serial0 socket --vga serial0
qm set 3000 --ciuser admin --cipassword securepassword
qm set 3000 --ipconfig0 ip=10.0.1.65/24,gw=10.0.1.1
qm resize 3000 scsi0 +40G
Once the VM was fully configured, we converted it into a template:
qm template 3000
This template became the foundation for all future VM deployments.
Deploying New VMs with Ansible
Now that we had a working template (3000), we needed an automated way to deploy new instances based on it. This was accomplished using Ansible and Proxmox's API.
Obfuscated Ansible Inventory Entry for Proxmox
We configured Ansible to connect to our Proxmox host (obfuscated for security):
[proxmox]
proxmox-host ansible_host=xxx.xxx.xxx.xxx ansible_user=root ansible_ssh_private_key_file=~/.ssh/proxmox_id_rsa
This allowed Ansible to communicate securely with Proxmox to issue API commands.
Final Ansible Playbook
The following Ansible playbook performs the entire process:
- Clones a VM from template
- Waits for cloning to complete
- Configures Cloud-Init
- Assigns a static IP
- Boots the VM
- name: Clone and Configure a New Proxmox VM
hosts: proxmox
gather_facts: no
tasks:
- name: Clone the VM from Template (3000)
uri:
url: "https://{{ inventory_hostname }}:8006/api2/json/nodes/mcp/qemu/3000/clone"
method: POST
headers:
Authorization: "PVEAPIToken=root@pam!your-api-token-here"
body_format: form-urlencoded
body:
newid: "2022"
name: "mariadb-core-1"
full: "1"
target: "mcp"
storage: "mcp-zfs1"
validate_certs: no
register: clone_response
- name: Wait for VM to be fully created (Check Lock Status)
uri:
url: "https://{{ inventory_hostname }}:8006/api2/json/nodes/mcp/qemu/2022/status/current"
method: GET
headers:
Authorization: "PVEAPIToken=root@pam!your-api-token-here"
validate_certs: no
register: vm_status
until: "'lock' not in vm_status.json.data"
retries: 20
delay: 5
- name: Attach Cloud-Init Drive (Required for User & SSH Injection)
uri:
url: "https://{{ inventory_hostname }}:8006/api2/json/nodes/mcp/qemu/2022/config"
method: POST
headers:
Authorization: "PVEAPIToken=root@pam!your-api-token-here"
body_format: form-urlencoded
body:
ide2: "local-lvm:cloudinit"
validate_certs: no
when: "'lock' not in vm_status.json.data"
- name: Set IP Address and Boot Configuration
uri:
url: "https://{{ inventory_hostname }}:8006/api2/json/nodes/mcp/qemu/2022/config"
method: POST
headers:
Authorization: "PVEAPIToken=root@pam!your-api-token-here"
body_format: form-urlencoded
body:
ipconfig0: "ip=10.0.1.65/24,gw=10.0.1.1"
boot: "c"
bootdisk: "scsi0"
machine: "q35"
validate_certs: no
when: "'lock' not in vm_status.json.data"
- name: Start the New VM
uri:
url: "https://{{ inventory_hostname }}:8006/api2/json/nodes/mcp/qemu/2022/status/start"
method: POST
headers:
Authorization: "PVEAPIToken=root@pam!your-api-token-here"
validate_certs: no
when: "'lock' not in vm_status.json.data"
Conclusion
This process successfully automated Proxmox VM deployment using Cloud-Init and Ansible, allowing for repeatable, scalable infrastructure provisioning. The final playbook does not install additional software—it simply ensures the VM boots correctly with predefined settings, static IP, and Cloud-Init configurations.
Next Steps
Now that VMs can be automatically created and started, the next logical step is to automate software installation via Ansible. This will allow for full stack provisioning—including database installations, application configurations, and monitoring setups.