AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

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 Automation Portal VM Appliance: Deploy on RHEL, KVM, OpenShift Virtualization, and VMware in AAP 2.7

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 docker_container Module: Manage Docker 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.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home