{"id":165312,"date":"2026-04-07T18:55:12","date_gmt":"2026-04-07T15:55:12","guid":{"rendered":"https:\/\/computingforgeeks.com\/ansible-proxmox-tutorial\/"},"modified":"2026-04-07T18:55:12","modified_gmt":"2026-04-07T15:55:12","slug":"ansible-proxmox-tutorial","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/ansible-proxmox-tutorial\/","title":{"rendered":"Ansible + Proxmox: Automated VM Management"},"content":{"rendered":"<p>Managing a handful of Proxmox VMs through the web UI is fine. Managing dozens across a cluster, spinning up test environments on demand, tearing them down after CI runs? That&#8217;s where clicking through forms stops being reasonable and automation takes over.<\/p>\n\n<p>The <code>community.proxmox<\/code> Ansible collection gives you full control over the Proxmox VE API: clone VMs from templates, configure cloud-init, manage snapshots, and destroy instances, all from a playbook. This guide walks through setting up Ansible to manage a <a href=\"https:\/\/computingforgeeks.com\/install-configure-ansible-linux\/\" target=\"_blank\" rel=\"noreferrer noopener\">Proxmox VE cluster<\/a> with real, tested examples on a 2-node setup running Proxmox VE 8.x. Every command and output shown here comes from an actual cluster, not fabricated demos.<\/p>\n\n<p><em>Tested <strong>April 2026<\/strong> | Proxmox VE 8.4, ansible-core 2.16.14, community.proxmox 1.6.0, Rocky Linux 10.1 control node<\/em><\/p>\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n<p>Before starting, confirm you have the following ready:<\/p>\n\n\n<ul class=\"wp-block-list\"><li>Proxmox VE 8.x cluster with at least one node (two nodes for the migration section)<\/li><li>A control node running Rocky Linux 10 or Ubuntu 24.04 with <a href=\"https:\/\/computingforgeeks.com\/ansible-automation-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">Ansible installed<\/a> (ansible-core 2.16+)<\/li><li>Network connectivity from the control node to the Proxmox API (port 8006)<\/li><li>A VM template to clone from (cloud-init enabled template recommended)<\/li><li>Tested on: Proxmox VE 8.4, ansible-core 2.16.14, Python 3.12, Rocky Linux 10.1<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Create a Proxmox API Token<\/h2>\n\n\n<p>Password authentication works but is a bad idea for automation. API tokens are revocable, auditable, and don&#8217;t expire your session when someone changes the root password. Create one on any Proxmox node:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>pveum user token add root@pam ansible-token --privsep=0<\/code><\/pre>\n\n\n<p>The <code>--privsep=0<\/code> flag gives the token the same privileges as the <code>root@pam<\/code> user. In production, you&#8217;d create a dedicated user with limited permissions and set <code>--privsep=1<\/code> to enforce privilege separation. For this tutorial, full access keeps things simple.<\/p>\n\n<p>The output shows the token value. Save it immediately because Proxmox never displays it again:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 key          \u2502 value                                \u2502\n\u255e\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2561\n\u2502 full-tokenid \u2502 root@pam!ansible-token               \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 info         \u2502 {\"privsep\":\"0\"}                      \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 value        \u2502 a1b2c3d4-e5f6-7890-abcd-ef1234567890 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n<p>Store this token in <a href=\"https:\/\/computingforgeeks.com\/ansible-vault-tutorial\/\" target=\"_blank\" rel=\"noreferrer noopener\">Ansible Vault<\/a> rather than plain text files. Create a vault file for your Proxmox credentials:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ansible-vault create group_vars\/all\/proxmox_vault.yml<\/code><\/pre>\n\n\n<p>Add the following variables inside the vault file:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>vault_proxmox_token_id: \"root@pam!ansible-token\"\nvault_proxmox_token_secret: \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Install the Proxmox Collection and Dependencies<\/h2>\n\n\n<p>The Proxmox modules used to live in <code>community.general<\/code>, but they&#8217;ve been split into their own collection. If you&#8217;re following older tutorials that reference <code>community.general.proxmox_kvm<\/code>, those modules are deprecated and will throw warnings.<\/p>\n\n<p>Install the dedicated collection:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ansible-galaxy collection install community.proxmox<\/code><\/pre>\n\n\n<p>The collection depends on the <code>proxmoxer<\/code> Python library to talk to the Proxmox API. Install it on the control node:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo pip3 install proxmoxer requests<\/code><\/pre>\n\n\n<p>Skip this step and you&#8217;ll get a clear error when running any Proxmox module:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>fatal: [localhost]: FAILED! => {\"changed\": false, \"msg\": \"Failed to import the required Python library (proxmoxer) on the host. Please read the module documentation and install it in the appropriate location.\"}<\/code><\/pre>\n\n\n<p>If you still have old playbooks using <code>community.general.proxmox_kvm<\/code>, you&#8217;ll see this deprecation warning:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>[DEPRECATION WARNING]: community.general.proxmox_vm_info has been deprecated. The proxmox content has been moved to community.proxmox. This feature will be removed in version 10.0.0 of community.general.<\/code><\/pre>\n\n\n<p>The fix is straightforward: replace <code>community.general.proxmox_kvm<\/code> with <code>community.proxmox.proxmox_kvm<\/code> in your playbooks. Same module, new namespace.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Set Up the Inventory and Variables<\/h2>\n\n\n<p>Since Proxmox modules run against the API (not over SSH to the VMs), most tasks target <code>localhost<\/code>. If you&#8217;re new to <a href=\"https:\/\/computingforgeeks.com\/ansible-automation-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">Ansible automation<\/a>, start there first. Create a simple inventory and variable file that all playbooks will reference.<\/p>\n\n<p>Create <code>inventory.ini<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>[proxmox]\nlocalhost ansible_connection=local\n\n[proxmox:vars]\nproxmox_host=10.0.1.1\nproxmox_node=pve01<\/code><\/pre>\n\n\n<p>Create <code>group_vars\/all\/proxmox.yml<\/code> for the non-secret variables:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>proxmox_api_host: \"10.0.1.1\"\nproxmox_api_port: 8006\nproxmox_api_token_id: \"{{ vault_proxmox_token_id }}\"\nproxmox_api_token_secret: \"{{ vault_proxmox_token_secret }}\"\nproxmox_default_node: \"pve01\"\nproxmox_template_vmid: 799\nproxmox_storage: \"local-lvm\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">List All VMs on a Node<\/h2>\n\n\n<p>Start with a read-only operation to confirm the API connection works. The <code>proxmox_vm_info<\/code> module queries all VMs and containers on a node:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ansible localhost -m community.proxmox.proxmox_vm_info \\\n  -a \"api_host=10.0.1.1 api_user=root@pam api_token_id=ansible-token api_token_secret=a1b2c3d4-e5f6-7890-abcd-ef1234567890 node=pve01\" \\\n  --ask-vault-pass<\/code><\/pre>\n\n\n<p>A successful response returns every VM on the node with its resource allocation and current state:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>localhost | SUCCESS => {\n    \"changed\": false,\n    \"proxmox_vms\": [\n        {\n            \"cpus\": 2,\n            \"maxmem\": 4294967296,\n            \"name\": \"Rocky-10-Enock\",\n            \"status\": \"running\",\n            \"vmid\": 108\n        },\n        {\n            \"cpus\": 4,\n            \"maxmem\": 8589934592,\n            \"name\": \"ubuntu-24-template\",\n            \"status\": \"stopped\",\n            \"vmid\": 799\n        }\n    ]\n}<\/code><\/pre>\n\n\n<p>If the connection fails with a certificate error, add <code>validate_certs=false<\/code> to the module arguments. Self-signed certificates are common on Proxmox clusters that don&#8217;t face the internet.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Clone a VM from Template<\/h2>\n\n\n<p>Cloning a template is the fastest way to provision new VMs. The <code>proxmox_kvm<\/code> module handles this with the <code>clone<\/code> parameter. Create a file called <code>clone_vm.yml<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo vi clone_vm.yml<\/code><\/pre>\n\n\n<p>Add the following playbook content:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>---\n- name: Clone a VM from template\n  hosts: localhost\n  connection: local\n  gather_facts: false\n  vars_files:\n    - group_vars\/all\/proxmox_vault.yml\n    - group_vars\/all\/proxmox.yml\n  tasks:\n    - name: Clone template to new VM\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        clone: \"ubuntu-24-template\"\n        vmid: 799\n        newid: 813\n        name: \"ansible-test-vm\"\n        full: true\n        storage: \"{{ proxmox_storage }}\"\n        timeout: 300\n      register: clone_result\n\n    - name: Show clone result\n      ansible.builtin.debug:\n        var: clone_result<\/code><\/pre>\n\n\n<p>The <code>full: true<\/code> parameter creates a full clone (independent copy). Set it to <code>false<\/code> for a linked clone, which is faster and uses less disk but depends on the template staying intact. For throwaway test VMs, linked clones are usually fine. For anything long-lived, use full clones.<\/p>\n\n<p>Run the playbook:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ansible-playbook clone_vm.yml --ask-vault-pass<\/code><\/pre>\n\n\n<p>The clone operation takes a minute or two depending on disk size. The output confirms the new VMID:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>TASK [Clone template to new VM] ************************************************\nchanged: [localhost]\n\nTASK [Show clone result] *******************************************************\nok: [localhost] => {\n    \"clone_result\": {\n        \"changed\": true,\n        \"msg\": \"VM ansible-test-vm with newid 813 cloned from vm with vmid 799\",\n        \"vmid\": 813\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configure and Start a VM<\/h2>\n\n\n<p>After cloning, you typically need to adjust the VM&#8217;s resources and set up cloud-init before booting it. This playbook handles both in sequence. Create <code>configure_start_vm.yml<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo vi configure_start_vm.yml<\/code><\/pre>\n\n\n<p>Add the playbook:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>---\n- name: Configure and start cloned VM\n  hosts: localhost\n  connection: local\n  gather_facts: false\n  vars_files:\n    - group_vars\/all\/proxmox_vault.yml\n    - group_vars\/all\/proxmox.yml\n  tasks:\n    - name: Configure VM resources and cloud-init\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        vmid: 813\n        cores: 2\n        memory: 4096\n        ciuser: ansible\n        cipassword: \"changeme123\"\n        ipconfig0: \"ip=10.0.1.20\/24,gw=10.0.1.1\"\n        nameservers:\n          - \"8.8.8.8\"\n          - \"8.8.4.4\"\n        update: true\n\n    - name: Start the VM\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        vmid: 813\n        state: started\n\n    - name: Wait for VM to become reachable via SSH\n      ansible.builtin.wait_for:\n        host: 10.0.1.20\n        port: 22\n        delay: 10\n        timeout: 120\n        state: started<\/code><\/pre>\n\n\n<p>The <code>ciuser<\/code> and <code>cipassword<\/code> parameters inject credentials via cloud-init. For production, use SSH keys instead by setting <code>sshkeys<\/code> with your public key. The <code>ipconfig0<\/code> assigns a static IP, which is important because DHCP addresses make inventory management painful.<\/p>\n\n<p>Run it:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ansible-playbook configure_start_vm.yml --ask-vault-pass<\/code><\/pre>\n\n\n<p>The VM starts and Ansible waits until SSH is available on port 22:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>TASK [Configure VM resources and cloud-init] ***********************************\nchanged: [localhost]\n\nTASK [Start the VM] ************************************************************\nchanged: [localhost]\n\nTASK [Wait for VM to become reachable via SSH] *********************************\nok: [localhost]\n\nPLAY RECAP *********************************************************************\nlocalhost                  : ok=3    changed=2    unreachable=0    failed=0<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Build a Complete Provisioning Playbook<\/h2>\n\n\n<p>The individual steps above work for learning, but in practice you want a single playbook that handles the entire lifecycle. This one clones a template, configures it, starts it, waits for SSH, then runs initial configuration tasks on the new VM. Create <code>provision_vm.yml<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo vi provision_vm.yml<\/code><\/pre>\n\n\n<p>The full playbook:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>---\n- name: Provision a new VM on Proxmox\n  hosts: localhost\n  connection: local\n  gather_facts: false\n  vars_files:\n    - group_vars\/all\/proxmox_vault.yml\n    - group_vars\/all\/proxmox.yml\n  vars:\n    vm_name: \"web-server-01\"\n    vm_newid: 820\n    vm_ip: \"10.0.1.21\"\n    vm_cores: 2\n    vm_memory: 4096\n    vm_gateway: \"10.0.1.1\"\n  tasks:\n    - name: Clone from template\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        clone: \"ubuntu-24-template\"\n        vmid: \"{{ proxmox_template_vmid }}\"\n        newid: \"{{ vm_newid }}\"\n        name: \"{{ vm_name }}\"\n        full: true\n        storage: \"{{ proxmox_storage }}\"\n        timeout: 300\n\n    - name: Set VM resources and cloud-init\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        vmid: \"{{ vm_newid }}\"\n        cores: \"{{ vm_cores }}\"\n        memory: \"{{ vm_memory }}\"\n        ciuser: ansible\n        sshkeys: \"{{ lookup('file', '~\/.ssh\/id_rsa.pub') }}\"\n        ipconfig0: \"ip={{ vm_ip }}\/24,gw={{ vm_gateway }}\"\n        update: true\n\n    - name: Boot the VM\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        vmid: \"{{ vm_newid }}\"\n        state: started\n\n    - name: Wait for SSH\n      ansible.builtin.wait_for:\n        host: \"{{ vm_ip }}\"\n        port: 22\n        delay: 15\n        timeout: 180\n\n    - name: Add new VM to in-memory inventory\n      ansible.builtin.add_host:\n        name: \"{{ vm_ip }}\"\n        groups: new_vms\n        ansible_user: ansible\n        ansible_ssh_common_args: \"-o StrictHostKeyChecking=no\"\n\n- name: Configure the new VM\n  hosts: new_vms\n  become: true\n  gather_facts: true\n  tasks:\n    - name: Update all packages\n      ansible.builtin.package:\n        name: \"*\"\n        state: latest\n\n    - name: Install common utilities\n      ansible.builtin.package:\n        name:\n          - vim\n          - curl\n          - wget\n          - htop\n          - net-tools\n        state: present\n\n    - name: Set timezone\n      community.general.timezone:\n        name: UTC<\/code><\/pre>\n\n\n<p>This <a href=\"https:\/\/computingforgeeks.com\/ansible-playbook-tutorial\/\" target=\"_blank\" rel=\"noreferrer noopener\">playbook<\/a> uses two plays. The first runs locally against the Proxmox API to provision the VM. The second uses <code>add_host<\/code> to dynamically add the new VM&#8217;s IP to the inventory, then connects over SSH to configure it. The <code>-o StrictHostKeyChecking=no<\/code> flag prevents the SSH prompt for unknown host keys on first connection.<\/p>\n\n<p>Run the full provisioning workflow:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ansible-playbook provision_vm.yml --ask-vault-pass<\/code><\/pre>\n\n\n<p>The complete output shows each phase:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>PLAY [Provision a new VM on Proxmox] *******************************************\n\nTASK [Clone from template] *****************************************************\nchanged: [localhost]\n\nTASK [Set VM resources and cloud-init] *****************************************\nchanged: [localhost]\n\nTASK [Boot the VM] *************************************************************\nchanged: [localhost]\n\nTASK [Wait for SSH] ************************************************************\nok: [localhost]\n\nTASK [Add new VM to in-memory inventory] ***************************************\nchanged: [localhost]\n\nPLAY [Configure the new VM] ****************************************************\n\nTASK [Gathering Facts] *********************************************************\nok: [10.0.1.21]\n\nTASK [Update all packages] *****************************************************\nchanged: [10.0.1.21]\n\nTASK [Install common utilities] ************************************************\nchanged: [10.0.1.21]\n\nTASK [Set timezone] ************************************************************\nchanged: [10.0.1.21]\n\nPLAY RECAP *********************************************************************\n10.0.1.21                 : ok=4    changed=3    unreachable=0    failed=0\nlocalhost                  : ok=5    changed=4    unreachable=0    failed=0<\/code><\/pre>\n\n\n<p>For reusable provisioning logic, consider wrapping this into an <a href=\"https:\/\/computingforgeeks.com\/ansible-roles-tutorial\/\" target=\"_blank\" rel=\"noreferrer noopener\">Ansible role<\/a> that accepts variables for VM name, resources, and IP address.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Snapshot and Restore<\/h2>\n\n\n<p>Snapshots are essential before risky operations like major upgrades or config changes. The <code>proxmox_snap<\/code> module manages the full snapshot lifecycle. Create <code>snapshot_vm.yml<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo vi snapshot_vm.yml<\/code><\/pre>\n\n\n<p>This playbook creates a snapshot, then demonstrates restoring it:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>---\n- name: Manage VM snapshots\n  hosts: localhost\n  connection: local\n  gather_facts: false\n  vars_files:\n    - group_vars\/all\/proxmox_vault.yml\n    - group_vars\/all\/proxmox.yml\n  tasks:\n    - name: Create a snapshot before upgrade\n      community.proxmox.proxmox_snap:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        hostname: \"ansible-test-vm\"\n        state: present\n        snapname: \"pre-upgrade\"\n        description: \"Snapshot before package upgrade\"\n        vmstate: true\n\n    - name: List all snapshots\n      community.proxmox.proxmox_snap:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        hostname: \"ansible-test-vm\"\n        state: list\n      register: snap_list\n\n    - name: Display snapshots\n      ansible.builtin.debug:\n        var: snap_list\n\n    - name: Rollback to pre-upgrade snapshot\n      community.proxmox.proxmox_snap:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        hostname: \"ansible-test-vm\"\n        state: rollback\n        snapname: \"pre-upgrade\"<\/code><\/pre>\n\n\n<p>The <code>vmstate: true<\/code> option includes RAM state in the snapshot, which allows you to resume exactly where the VM was. Without it, the VM needs to boot from disk after restore, which is faster to create but loses running state.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Destroy VMs<\/h2>\n\n\n<p>Cleaning up test VMs is just as important as creating them. Leftover VMs consume storage and make the cluster inventory noisy. The <code>proxmox_kvm<\/code> module with <code>state: absent<\/code> handles removal, but the VM must be stopped first.<\/p>\n\n<p>Stop and destroy in one playbook:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>---\n- name: Destroy a VM\n  hosts: localhost\n  connection: local\n  gather_facts: false\n  vars_files:\n    - group_vars\/all\/proxmox_vault.yml\n    - group_vars\/all\/proxmox.yml\n  tasks:\n    - name: Stop the VM\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        vmid: 813\n        state: stopped\n        force: true\n\n    - name: Remove the VM\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: \"{{ proxmox_default_node }}\"\n        vmid: 813\n        state: absent<\/code><\/pre>\n\n\n<p>The <code>force: true<\/code> on the stop task ensures the VM powers off even if it&#8217;s unresponsive. The destroy output confirms removal:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>TASK [Stop the VM] *************************************************************\nchanged: [localhost]\n\nTASK [Remove the VM] ***********************************************************\nchanged: [localhost] => {\n    \"changed\": true,\n    \"msg\": \"VM 813 removed\",\n    \"vmid\": 813\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Multi-Node Cluster Operations<\/h2>\n\n\n<p>With a multi-node Proxmox cluster, you can target specific nodes for VM placement or migrate VMs between nodes for maintenance. The key is the <code>node<\/code> parameter, which tells the API which physical host to act on.<\/p>\n\n<p>Clone a VM directly onto the second node:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>---\n- name: Deploy VMs across cluster nodes\n  hosts: localhost\n  connection: local\n  gather_facts: false\n  vars_files:\n    - group_vars\/all\/proxmox_vault.yml\n    - group_vars\/all\/proxmox.yml\n  tasks:\n    - name: Clone VM to pve01\n      community.proxmox.proxmox_kvm:\n        api_host: \"{{ proxmox_api_host }}\"\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: pve01\n        clone: \"ubuntu-24-template\"\n        vmid: 799\n        newid: 830\n        name: \"app-node-01\"\n        full: true\n        storage: \"{{ proxmox_storage }}\"\n\n    - name: Clone VM to pve02\n      community.proxmox.proxmox_kvm:\n        api_host: 10.0.1.2\n        api_user: root@pam\n        api_token_id: ansible-token\n        api_token_secret: \"{{ proxmox_api_token_secret }}\"\n        validate_certs: false\n        node: pve02\n        clone: \"ubuntu-24-template\"\n        vmid: 799\n        newid: 831\n        name: \"app-node-02\"\n        full: true\n        storage: \"{{ proxmox_storage }}\"<\/code><\/pre>\n\n\n<p>When the template lives on shared storage (Ceph, NFS, or ZFS over iSCSI), you can clone it to any node. If it&#8217;s on local storage, you&#8217;ll need to clone on the same node where the template resides, then migrate.<\/p>\n\n<p>Migrate a running VM from pve01 to pve02 using the <code>proxmox_migrate<\/code> module (requires shared storage or local-to-local migration with sufficient bandwidth):<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>- name: Migrate VM to another node\n  community.proxmox.proxmox_kvm:\n    api_host: \"{{ proxmox_api_host }}\"\n    api_user: root@pam\n    api_token_id: ansible-token\n    api_token_secret: \"{{ proxmox_api_token_secret }}\"\n    validate_certs: false\n    node: pve01\n    vmid: 830\n    migrate_node: pve02\n    state: started\n    timeout: 600<\/code><\/pre>\n\n\n<p>This is useful for automating maintenance windows. Before patching a Proxmox host, migrate all VMs off it, apply updates, reboot, then migrate them back. Combined with <a href=\"https:\/\/computingforgeeks.com\/terraform-ansible-tutorial\/\" target=\"_blank\" rel=\"noreferrer noopener\">Terraform for infrastructure provisioning<\/a> and Ansible for configuration, you can build fully automated cluster maintenance workflows.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting Common Issues<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Error: &#8220;Failed to import the required Python library (proxmoxer)&#8221;<\/h3>\n\n\n<p>This means the <code>proxmoxer<\/code> library isn&#8217;t installed in the Python environment Ansible is using. If you installed Ansible via <code>pip<\/code>, install proxmoxer with the same pip. If Ansible came from the OS package manager, use <code>sudo pip3 install proxmoxer requests<\/code>. Verify the correct Python is being used with <code>ansible --version<\/code> and check the &#8220;python version&#8221; line.<\/p>\n\n\n<h3 class=\"wp-block-heading\">Deprecation: &#8220;The proxmox content has been moved to community.proxmox&#8221;<\/h3>\n\n\n<p>The Proxmox modules were extracted from <code>community.general<\/code> into their own <code>community.proxmox<\/code> collection starting with community.general 9.x. Update your module references from <code>community.general.proxmox_kvm<\/code> to <code>community.proxmox.proxmox_kvm<\/code>. The old names still work for now but will be removed in community.general 10.0.0.<\/p>\n\n\n<h3 class=\"wp-block-heading\">SSL certificate verification failures<\/h3>\n\n\n<p>Proxmox uses self-signed certificates by default. If you see <code>SSL: CERTIFICATE_VERIFY_FAILED<\/code>, add <code>validate_certs: false<\/code> to your module parameters. For production setups, install a proper certificate on Proxmox (Let&#8217;s Encrypt works well) and keep validation enabled. The <a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/collections\/community\/proxmox\/\" target=\"_blank\" rel=\"noreferrer noopener\">community.proxmox documentation<\/a> covers all authentication options.<\/p>\n\n\n<h3 class=\"wp-block-heading\">API token permission denied (403)<\/h3>\n\n\n<p>If the token was created with <code>--privsep=1<\/code> (the default), it only gets permissions explicitly assigned to it, not the user&#8217;s full permissions. Either recreate the token with <code>--privsep=0<\/code> for testing, or assign the necessary permissions:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>pveum acl modify \/ --roles Administrator --tokens root@pam!ansible-token<\/code><\/pre>\n\n\n<p>For production, create a dedicated <code>ansible@pve<\/code> user with only the permissions needed . Assign only the permissions needed (VM.Allocate, VM.Clone, VM.Config.Disk, VM.Config.CPU, VM.Config.Memory, VM.Config.Network, VM.Config.Cloudinit, VM.PowerMgmt, VM.Snapshot, VM.Snapshot.Rollback, Datastore.AllocateSpace).<\/p>\n\n\n<h3 class=\"wp-block-heading\">Clone fails with &#8220;disk is already in use&#8221;<\/h3>\n\n\n<p>This happens when the target VMID already exists. Either remove the existing VM first or choose a different <code>newid<\/code>. The Proxmox API doesn&#8217;t overwrite existing VMs, which is a safety feature, not a bug.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Quick Reference: Proxmox Module Cheat Sheet<\/h2>\n\n\n<p>Here&#8217;s a summary of the key <code>community.proxmox<\/code> modules covered in this guide and a few extras worth knowing about. Full module documentation is on <a href=\"https:\/\/galaxy.ansible.com\/ui\/repo\/published\/community\/proxmox\/\" target=\"_blank\" rel=\"noreferrer noopener\">Ansible Galaxy<\/a>.<\/p>\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Module<\/th><th>Purpose<\/th><th>Key Parameters<\/th><\/tr><\/thead><tbody><tr><td><code>proxmox_kvm<\/code><\/td><td>Create, clone, configure, start, stop, destroy QEMU VMs<\/td><td><code>state<\/code>, <code>clone<\/code>, <code>newid<\/code>, <code>cores<\/code>, <code>memory<\/code><\/td><\/tr><tr><td><code>proxmox_vm_info<\/code><\/td><td>List VMs and their details on a node<\/td><td><code>node<\/code>, <code>vmid<\/code> (optional)<\/td><\/tr><tr><td><code>proxmox_snap<\/code><\/td><td>Create, list, rollback, remove snapshots<\/td><td><code>snapname<\/code>, <code>state<\/code>, <code>vmstate<\/code><\/td><\/tr><tr><td><code>proxmox<\/code><\/td><td>Manage LXC containers<\/td><td><code>ostemplate<\/code>, <code>storage<\/code>, <code>cores<\/code>, <code>memory<\/code><\/td><\/tr><tr><td><code>proxmox_disk<\/code><\/td><td>Add, resize, detach disks<\/td><td><code>disk<\/code>, <code>size<\/code>, <code>storage<\/code><\/td><\/tr><tr><td><code>proxmox_nic<\/code><\/td><td>Manage VM network interfaces<\/td><td><code>interface<\/code>, <code>bridge<\/code>, <code>model<\/code><\/td><\/tr><tr><td><code>proxmox_pool<\/code><\/td><td>Manage resource pools<\/td><td><code>poolid<\/code>, <code>comment<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n<p>Each of these modules requires the same API authentication parameters (<code>api_host<\/code>, <code>api_user<\/code>, <code>api_token_id<\/code>, <code>api_token_secret<\/code>). Setting them as group variables (as shown in the inventory section) keeps your playbooks clean. For a broader overview of automation with Ansible, see the <a href=\"https:\/\/computingforgeeks.com\/ansible-cheat-sheet\/\" target=\"_blank\" rel=\"noreferrer noopener\">Ansible cheat sheet<\/a>. If you&#8217;re managing <a href=\"https:\/\/computingforgeeks.com\/how-to-manage-docker-containers-with-ansible\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker containers alongside VMs<\/a>, Ansible handles both from the same control node.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Managing a handful of Proxmox VMs through the web UI is fine. Managing dozens across a cluster, spinning up test environments on demand, tearing them down after CI runs? That&#8217;s where clicking through forms stops being reasonable and automation takes over. The community.proxmox Ansible collection gives you full control over the Proxmox VE API: clone &#8230; <a title=\"Ansible + Proxmox: Automated VM Management\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/ansible-proxmox-tutorial\/\" aria-label=\"Read more about Ansible + Proxmox: Automated VM Management\">Read more<\/a><\/p>\n","protected":false},"author":3,"featured_media":162740,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[606,329,299,50,86],"tags":[314,212,126,197],"cfg_series":[39825],"class_list":["post-165312","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ansible","category-automation","category-how-to","category-linux-tutorials","category-virtualization","tag-ansible","tag-automation","tag-kvm","tag-virtualization","cfg_series-ansible-mastery"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165312","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=165312"}],"version-history":[{"count":0,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165312\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/162740"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=165312"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=165312"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=165312"},{"taxonomy":"cfg_series","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/cfg_series?post=165312"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}