Ansible for Proxmox: Automate VM and Container Management Complete Guide
By Luca Berton · Published 2024-01-01 · Category: installation
Automate Proxmox VE with Ansible. Create VMs and LXC containers, manage storage, configure networking, automate templates, cluster operations, backups.
Why Automate Proxmox with Ansible?
Proxmox VE is one of the most popular open-source virtualization platforms, widely used in homelabs, SMBs, and enterprises. Managing VMs and containers manually through the web UI works for a few machines — but when you need to provision dozens of VMs, maintain consistent configurations, or rebuild environments reliably, you need automation.
Ansible's community.general collection includes Proxmox modules that let you manage the full lifecycle: create VMs and LXC containers, configure networks and storage, manage templates, handle snapshots and backups — all from YAML playbooks.
See also: Ansible on Proxmox VE 8 Automation Complete Guide
Prerequisites
# Install the collection
ansible-galaxy collection install community.general
# Install proxmoxer Python library (required on control node)
pip install proxmoxer requests
Authentication
# group_vars/proxmox.yml
proxmox_host: "pve1.home.lab"
proxmox_user: "ansible@pam"
proxmox_password: "{{ vault_proxmox_password }}"
# Or use API token (recommended):
proxmox_token_id: "ansible@pam!automation"
proxmox_token_secret: "{{ vault_proxmox_token }}"
Create Virtual Machines
Clone from Template
---
- name: Create VMs from template
hosts: localhost
gather_facts: false
vars:
proxmox_host: "pve1.home.lab"
proxmox_token_id: "ansible@pam!automation"
proxmox_token_secret: "{{ vault_proxmox_token }}"
template_vmid: 9000
vms:
- { name: "web-01", vmid: 100, cores: 2, memory: 4096, ip: "10.0.1.10/24" }
- { name: "web-02", vmid: 101, cores: 2, memory: 4096, ip: "10.0.1.11/24" }
- { name: "db-01", vmid: 200, cores: 4, memory: 8192, ip: "10.0.1.20/24" }
tasks:
- name: Clone VM from template
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
clone: "ubuntu-template"
name: "{{ item.name }}"
vmid: "{{ item.vmid }}"
full: true
storage: "local-lvm"
timeout: 300
loop: "{{ vms }}"
- name: Configure VM resources
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
vmid: "{{ item.vmid }}"
cores: "{{ item.cores }}"
memory: "{{ item.memory }}"
update: true
loop: "{{ vms }}"
- name: Configure cloud-init networking
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
vmid: "{{ item.vmid }}"
ipconfig:
ipconfig0: "ip={{ item.ip }},gw=10.0.1.1"
nameservers: "10.0.1.1"
searchdomains: "home.lab"
sshkeys: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
update: true
loop: "{{ vms }}"
- name: Start VMs
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
vmid: "{{ item.vmid }}"
state: started
loop: "{{ vms }}"
Create VM from Scratch
- name: Create VM with specific hardware
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
name: "custom-vm"
vmid: 300
cores: 4
sockets: 1
memory: 8192
balloon: 4096
bios: ovmf
machine: q35
cpu: host
ostype: l26
scsihw: virtio-scsi-single
scsi:
scsi0: "local-lvm:32,iothread=1,discard=on,ssd=1"
ide:
ide2: "local:iso/ubuntu-24.04-server.iso,media=cdrom"
net:
net0: "virtio,bridge=vmbr0,firewall=1"
efidisk0:
storage: local-lvm
format: raw
efitype: 4m
pre_enrolled_keys: false
agent: true
onboot: true
state: present
See also: Ansible docker_container Module: Manage Docker Containers (Guide)
Create LXC Containers
---
- name: Create LXC containers
hosts: localhost
gather_facts: false
vars:
containers:
- { name: "pihole", vmid: 400, template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst", cores: 1, memory: 512, disk: 8, ip: "10.0.1.40/24" }
- { name: "nginx-proxy", vmid: 401, template: "local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst", cores: 2, memory: 1024, disk: 16, ip: "10.0.1.41/24" }
- { name: "monitoring", vmid: 402, template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst", cores: 2, memory: 2048, disk: 32, ip: "10.0.1.42/24" }
tasks:
- name: Create LXC container
community.general.proxmox:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
hostname: "{{ item.name }}"
vmid: "{{ item.vmid }}"
ostemplate: "{{ item.template }}"
storage: "local-lvm"
disk: "{{ item.disk }}"
cores: "{{ item.cores }}"
memory: "{{ item.memory }}"
swap: 512
netif: '{"net0":"name=eth0,bridge=vmbr0,ip={{ item.ip }},gw=10.0.1.1"}'
nameserver: "10.0.1.1"
searchdomain: "home.lab"
pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
unprivileged: true
features:
- nesting=1
onboot: true
state: present
loop: "{{ containers }}"
- name: Start containers
community.general.proxmox:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
vmid: "{{ item.vmid }}"
state: started
loop: "{{ containers }}"
Create Cloud-Init Templates
---
- name: Create Ubuntu cloud-init template
hosts: proxmox_nodes
become: true
vars:
template_vmid: 9000
cloud_image_url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
tasks:
- name: Download cloud image
ansible.builtin.get_url:
url: "{{ cloud_image_url }}"
dest: /tmp/ubuntu-cloud.img
- name: Create VM for template
ansible.builtin.command:
cmd: >
qm create {{ template_vmid }}
--name ubuntu-template
--memory 2048 --cores 2
--net0 virtio,bridge=vmbr0
--scsihw virtio-scsi-single
--agent enabled=1
--ostype l26
creates: /etc/pve/qemu-server/{{ template_vmid }}.conf
- name: Import cloud image as disk
ansible.builtin.command:
cmd: qm importdisk {{ template_vmid }} /tmp/ubuntu-cloud.img local-lvm
register: import_result
changed_when: "'Successfully imported' in import_result.stdout"
- name: Attach disk to VM
ansible.builtin.command:
cmd: qm set {{ template_vmid }} --scsi0 local-lvm:vm-{{ template_vmid }}-disk-0,discard=on,ssd=1,iothread=1
- name: Add cloud-init drive
ansible.builtin.command:
cmd: qm set {{ template_vmid }} --ide2 local-lvm:cloudinit
- name: Set boot order
ansible.builtin.command:
cmd: qm set {{ template_vmid }} --boot order=scsi0
- name: Resize disk
ansible.builtin.command:
cmd: qm resize {{ template_vmid }} scsi0 32G
- name: Convert to template
ansible.builtin.command:
cmd: qm template {{ template_vmid }}
See also: Ansible Molecule Docker: Test Roles in Containers (Guide)
Manage Snapshots
- name: Create snapshot before upgrade
community.general.proxmox_snap:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
hostname: "web-01"
snapname: "pre-upgrade-{{ ansible_date_time.date }}"
description: "Snapshot before OS upgrade"
vmstate: false
state: present
- name: Rollback to snapshot
community.general.proxmox_snap:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
hostname: "web-01"
snapname: "pre-upgrade-{{ ansible_date_time.date }}"
state: rollback
when: upgrade_failed | default(false)
Dynamic Inventory
Use the Proxmox dynamic inventory plugin to automatically discover VMs and containers:
# proxmox.yml (inventory file)
plugin: community.general.proxmox
url: https://pve1.home.lab:8006
token_id: "ansible@pam!automation"
token_secret: "{{ vault_proxmox_token }}"
validate_certs: false
# Group by tags
groups:
webservers: "'web' in (proxmox_tags_parsed | default([]))"
databases: "'db' in (proxmox_tags_parsed | default([]))"
# Set connection variables
compose:
ansible_host: proxmox_ipconfig0.ip | default(proxmox_net0.ip) | ansible.utils.ipaddr('address')
# Only include running VMs
filters:
- proxmox_status == "running"
# Include both VMs and containers
want_facts: true
want_proxmox_nodes_ans_groups: true
# Test dynamic inventory
ansible-inventory -i proxmox.yml --graph
ansible-inventory -i proxmox.yml --list
Backup Automation
---
- name: Automated Proxmox backups
hosts: localhost
gather_facts: false
vars:
backup_storage: "nfs-backup"
backup_mode: "snapshot"
vms_to_backup:
- 100 # web-01
- 101 # web-02
- 200 # db-01
tasks:
- name: Create backup for each VM
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
vmid: "{{ item }}"
state: current
loop: "{{ vms_to_backup }}"
- name: Trigger vzdump backup via API
ansible.builtin.uri:
url: "https://{{ proxmox_host }}:8006/api2/json/nodes/pve1/vzdump"
method: POST
headers:
Authorization: "PVEAPIToken={{ proxmox_token_id }}={{ proxmox_token_secret }}"
body_format: form-urlencoded
body:
vmid: "{{ item }}"
storage: "{{ backup_storage }}"
mode: "{{ backup_mode }}"
compress: zstd
notes-template: "Ansible backup {{ ansible_date_time.iso8601 }}"
validate_certs: false
loop: "{{ vms_to_backup }}"
- name: Clean old backups (keep last 7)
ansible.builtin.shell: |
# List backups sorted by date, remove all but last 7
ls -t /mnt/backup/dump/vzdump-qemu-{{ item }}-*.zst 2>/dev/null | tail -n +8 | xargs -r rm -f
delegate_to: "{{ proxmox_host }}"
loop: "{{ vms_to_backup }}"
Full Infrastructure as Code Example
# homelab.yml - Complete homelab infrastructure
---
- name: Deploy complete homelab
hosts: localhost
gather_facts: false
vars_files:
- vars/proxmox-credentials.yml
- vars/network.yml
vars:
infrastructure:
templates:
- { vmid: 9000, name: "ubuntu-24.04", image: "noble-server-cloudimg-amd64.img" }
- { vmid: 9001, name: "debian-12", image: "debian-12-genericcloud-amd64.qcow2" }
vms:
- { name: "k3s-master", vmid: 110, cores: 4, memory: 8192, disk: 64, ip: "10.0.1.110/24", tags: ["k8s", "master"] }
- { name: "k3s-worker-1", vmid: 111, cores: 4, memory: 8192, disk: 64, ip: "10.0.1.111/24", tags: ["k8s", "worker"] }
- { name: "k3s-worker-2", vmid: 112, cores: 4, memory: 8192, disk: 64, ip: "10.0.1.112/24", tags: ["k8s", "worker"] }
- { name: "nas", vmid: 120, cores: 2, memory: 4096, disk: 32, ip: "10.0.1.120/24", tags: ["storage"] }
containers:
- { name: "pihole", vmid: 400, cores: 1, memory: 512, disk: 8, ip: "10.0.1.2/24", tags: ["dns"] }
- { name: "wireguard", vmid: 401, cores: 1, memory: 256, disk: 4, ip: "10.0.1.3/24", tags: ["vpn"] }
- { name: "prometheus", vmid: 402, cores: 2, memory: 2048, disk: 64, ip: "10.0.1.42/24", tags: ["monitoring"] }
- { name: "grafana", vmid: 403, cores: 1, memory: 1024, disk: 16, ip: "10.0.1.43/24", tags: ["monitoring"] }
tasks:
- name: Create VMs
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
clone: "ubuntu-24.04"
name: "{{ item.name }}"
vmid: "{{ item.vmid }}"
cores: "{{ item.cores }}"
memory: "{{ item.memory }}"
full: true
storage: "local-lvm"
ipconfig:
ipconfig0: "ip={{ item.ip }},gw=10.0.1.1"
sshkeys: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
tags: "{{ item.tags | join(';') }}"
onboot: true
state: present
loop: "{{ infrastructure.vms }}"
- name: Create LXC containers
community.general.proxmox:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
hostname: "{{ item.name }}"
vmid: "{{ item.vmid }}"
ostemplate: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
storage: "local-lvm"
disk: "{{ item.disk }}"
cores: "{{ item.cores }}"
memory: "{{ item.memory }}"
netif: '{"net0":"name=eth0,bridge=vmbr0,ip={{ item.ip }},gw=10.0.1.1"}'
pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
unprivileged: true
features:
- nesting=1
tags: "{{ item.tags | join(';') }}"
onboot: true
state: present
loop: "{{ infrastructure.containers }}"
- name: Start all VMs
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
vmid: "{{ item.vmid }}"
state: started
loop: "{{ infrastructure.vms }}"
- name: Start all containers
community.general.proxmox:
api_host: "{{ proxmox_host }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "pve1"
vmid: "{{ item.vmid }}"
state: started
loop: "{{ infrastructure.containers }}"
Proxmox Cluster Management
---
- name: Configure Proxmox cluster nodes
hosts: proxmox_nodes
become: true
tasks:
- name: Configure cluster networking
ansible.builtin.template:
src: interfaces.j2
dest: /etc/network/interfaces
notify: restart networking
- name: Set NTP synchronization
ansible.builtin.lineinfile:
path: /etc/chrony/chrony.conf
line: "server {{ ntp_server }} iburst"
notify: restart chrony
- name: Configure backup schedule
ansible.builtin.copy:
content: |
# /etc/pve/vzdump.cron
# Backup all VMs at 2 AM daily
0 2 * * * root vzdump --all --mode snapshot --storage nfs-backup --compress zstd --mailnotification failure --mailto admin@company.com
dest: /etc/pve/vzdump.cron
mode: '0644'
handlers:
- name: restart networking
ansible.builtin.systemd:
name: networking
state: restarted
- name: restart chrony
ansible.builtin.systemd:
name: chrony
state: restarted
FAQ
Which Ansible module manages Proxmox VMs?
Use community.general.proxmox_kvm for KVM virtual machines and community.general.proxmox for LXC containers. Both require the proxmoxer Python library on your Ansible control node. Install with pip install proxmoxer requests.
Can Ansible manage Proxmox clusters?
Yes. Use the Proxmox modules for VM/container operations across any node in the cluster, and standard Ansible modules (template, systemd, package) for node configuration. The dynamic inventory plugin discovers all VMs/containers across the cluster automatically.
How do I use cloud-init templates with Ansible and Proxmox?
Create a cloud-init enabled template VM (import cloud image, add cloud-init drive, convert to template), then use community.general.proxmox_kvm with clone to create VMs from it. Set ipconfig, sshkeys, and nameservers parameters for automatic network and SSH configuration.
Is the Proxmox API token or password more secure?
API tokens are more secure — they can have limited permissions, don't expire with password changes, and can be revoked independently. Create a token: Datacenter → Permissions → API Tokens. Assign only the permissions needed (VM.Allocate, VM.Clone, Datastore.AllocateSpace).
Conclusion
Ansible's Proxmox modules turn your virtualization platform into a fully automated infrastructure-as-code environment. Define your VMs, containers, networks, and storage in YAML, version control the configuration, and provision or rebuild entire environments with a single command. Combined with dynamic inventory and cloud-init templates, you get a scalable, repeatable infrastructure pipeline for everything from homelabs to production Proxmox clusters.
Related Articles
• Ansible vs Terraform: Complete Comparison Guide • Ansible VMware Automation Guide • Ansible Dynamic Inventory Complete Guide • Ansible for Kubernetes • Install Ansible Complete GuideCategory: installation