home / skills / basher83 / lunar-claude / ansible-proxmox

This skill automates Proxmox VE using the community.proxmox collection, preferring native modules to CLI, for VM creation, user management, and cluster

npx playbooks add skill basher83/lunar-claude --skill ansible-proxmox

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
9.2 KB
---
name: ansible-proxmox
description: >
  This skill should be used when automating Proxmox VE with Ansible, creating VMs
  or templates, managing Proxmox clusters, using community.proxmox collection,
  or deciding between native modules and CLI commands (pvecm, pveceph, qm).
---

# Ansible Proxmox Integration

Expert Proxmox automation using community.proxmox collection with minimal CLI usage.

## Module Selection

### Prefer Native Modules

Use `community.proxmox` modules when available:

| Operation | Use Module | NOT CLI |
|-----------|------------|---------|
| Create VM | `community.proxmox.proxmox_kvm` | `qm create` |
| Clone VM | `community.proxmox.proxmox_kvm` | `qm clone` |
| Manage users | `community.proxmox.proxmox_user` | `pveum user` |
| Manage groups | `community.proxmox.proxmox_group` | `pveum group` |
| Manage pools | `community.proxmox.proxmox_pool` | `pveum pool` |
| Manage ACLs | `community.proxmox.proxmox_acl` | `pveum acl` |
| Storage | `community.proxmox.proxmox_storage` | `pvesm` |

### When CLI is Required

Some operations lack native modules:

| Operation | Requires CLI | Reason |
|-----------|--------------|--------|
| Cluster create | `pvecm create` | No module exists |
| Cluster join | `pvecm add` | No module exists |
| CEPH init | `pveceph init` | Complex workflow |
| CEPH OSD | `pveceph osd create` | Complex workflow |

## Native Module Examples

### Create VM from Template

```yaml
- name: Create VM from template
  community.proxmox.proxmox_kvm:
    api_host: "{{ proxmox_api_host }}"
    api_user: "{{ proxmox_api_user }}"
    api_token_id: "{{ proxmox_token_id }}"
    api_token_secret: "{{ proxmox_token_secret }}"
    node: "{{ proxmox_node }}"
    vmid: "{{ vm_id }}"
    name: "{{ vm_name }}"
    clone: "{{ template_name }}"
    full: true
    storage: local-lvm
    memory: "{{ vm_memory | default(4096) }}"
    cores: "{{ vm_cores | default(2) }}"
    state: present
  delegate_to: localhost
```

### Manage Proxmox User

```yaml
- name: Create Terraform user
  community.proxmox.proxmox_user:
    api_host: "{{ proxmox_api_host }}"
    api_user: "root@pam"
    api_password: "{{ proxmox_root_password }}"
    userid: "terraform@pve"
    comment: "Terraform automation user"
    groups:
      - automation
    state: present
  no_log: true
  delegate_to: localhost
```

### Configure ACLs

```yaml
- name: Grant terraform permissions
  community.proxmox.proxmox_acl:
    api_host: "{{ proxmox_api_host }}"
    api_user: "{{ proxmox_api_user }}"
    api_token_id: "{{ proxmox_token_id }}"
    api_token_secret: "{{ proxmox_token_secret }}"
    path: "/"
    users:
      - terraform@pve
    roles:
      - Administrator
    state: present
  delegate_to: localhost
```

## CLI with Idempotency

When CLI is required, add proper idempotency controls:

### Cluster Formation

```yaml
- name: Check existing cluster status
  ansible.builtin.command: pvecm status
  register: cluster_status
  failed_when: false
  changed_when: false

- name: Set cluster facts
  ansible.builtin.set_fact:
    is_cluster_member: "{{ cluster_status.rc == 0 }}"
    in_target_cluster: "{{ cluster_name in cluster_status.stdout }}"

- name: Create cluster on primary node
  ansible.builtin.command: pvecm create {{ cluster_name }}
  when:
    - inventory_hostname == groups['proxmox'][0]
    - not in_target_cluster
  register: cluster_create
  changed_when: cluster_create.rc == 0

- name: Join cluster on secondary nodes
  ansible.builtin.command: pvecm add {{ hostvars[groups['proxmox'][0]].ansible_host }}
  when:
    - inventory_hostname != groups['proxmox'][0]
    - not is_cluster_member
  register: cluster_join
  changed_when: cluster_join.rc == 0
```

### API Token Creation

```yaml
- name: Create API token
  ansible.builtin.command: >
    pveum user token add {{ username }}@pam {{ token_name }}
    --privsep 0
  register: token_result
  changed_when: "'already exists' not in token_result.stderr"
  failed_when:
    - token_result.rc != 0
    - "'already exists' not in token_result.stderr"
  no_log: true
```

## Authentication Patterns

### API Token (Recommended)

```yaml
# Store token in variables or Infisical
proxmox_api_host: "192.168.1.10"
proxmox_api_user: "terraform@pve"
proxmox_token_id: "automation"
proxmox_token_secret: "{{ lookup('infisical', 'PROXMOX_TOKEN_SECRET') }}"

- name: Create VM
  community.proxmox.proxmox_kvm:
    api_host: "{{ proxmox_api_host }}"
    api_user: "{{ proxmox_api_user }}"
    api_token_id: "{{ proxmox_token_id }}"
    api_token_secret: "{{ proxmox_token_secret }}"
    # ... rest of config
```

### Root Password (Local Operations)

For operations on Proxmox nodes themselves:

```yaml
- name: Retrieve root password
  ansible.builtin.include_tasks: tasks/infisical-secret-lookup.yml
  vars:
    secret_name: 'PROXMOX_ROOT_PASSWORD'
    secret_var_name: 'proxmox_root_password'

- name: Configure via API
  community.proxmox.proxmox_user:
    api_host: "{{ inventory_hostname }}"
    api_user: "root@pam"
    api_password: "{{ proxmox_root_password }}"
    # ... config
  no_log: true
```

## Cluster Operations

### Idempotent Cluster Status Check

```yaml
- name: Get cluster status
  ansible.builtin.command: pvecm status
  register: cluster_status
  changed_when: false
  failed_when: false

- name: Get cluster nodes
  ansible.builtin.command: pvecm nodes
  register: cluster_nodes
  changed_when: false
  failed_when: false
  when: cluster_status.rc == 0

- name: Set cluster facts
  ansible.builtin.set_fact:
    cluster_exists: "{{ cluster_status.rc == 0 }}"
    cluster_node_count: "{{ cluster_nodes.stdout_lines | length | default(0) }}"
    is_quorate: "{{ 'Quorate: Yes' in cluster_status.stdout | default('') }}"
```

### Verify Cluster Health

```yaml
- name: Verify cluster quorum
  ansible.builtin.command: pvecm status
  register: cluster_health
  changed_when: false
  failed_when: "'Quorate: Yes' not in cluster_health.stdout"

- name: Verify expected node count
  ansible.builtin.command: pvecm nodes
  register: nodes_check
  changed_when: false
  failed_when: nodes_check.stdout_lines | length != groups['proxmox'] | length
```

## CEPH Integration

### Initialize CEPH

```yaml
- name: Check if CEPH is initialized
  ansible.builtin.command: pveceph status
  register: ceph_status
  changed_when: false
  failed_when: false

- name: Initialize CEPH
  ansible.builtin.command: >
    pveceph init --network {{ ceph_network }}
  when:
    - inventory_hostname == groups['proxmox'][0]
    - ceph_status.rc != 0
  register: ceph_init
  changed_when: ceph_init.rc == 0
```

### Create OSD

```yaml
- name: Check if OSD exists on device
  ansible.builtin.command: >
    pveceph osd list
  register: osd_list
  changed_when: false

- name: Create OSD
  ansible.builtin.command: >
    pveceph osd create {{ item }}
  loop: "{{ ceph_osd_devices }}"
  when: item not in osd_list.stdout
  register: osd_create
  changed_when: osd_create.rc == 0
```

## Network Configuration

Use `community.general.interfaces_file` for network config:

```yaml
- name: Configure VLAN-aware bridge
  community.general.interfaces_file:
    iface: vmbr1
    option: bridge-vlan-aware
    value: "yes"
    backup: true
    state: present
  notify: reload network

- name: Set bridge ports
  community.general.interfaces_file:
    iface: vmbr1
    option: bridge-ports
    value: "bond0"
    backup: true
    state: present
  notify: reload network
```

## Anti-Patterns

### Using CLI When Module Exists

```yaml
# BAD - Module exists for this
- name: Create user
  ansible.builtin.command: pveum user add terraform@pve

# GOOD
- name: Create user
  community.proxmox.proxmox_user:
    api_host: "{{ proxmox_api_host }}"
    api_user: "root@pam"
    api_password: "{{ password }}"
    userid: "terraform@pve"
    state: present
```

### Missing Idempotency on CLI

```yaml
# BAD - Will fail on second run
- name: Create cluster
  ansible.builtin.command: pvecm create MyCluster

# GOOD
- name: Check cluster status
  ansible.builtin.command: pvecm status
  register: cluster_check
  changed_when: false
  failed_when: false

- name: Create cluster
  ansible.builtin.command: pvecm create MyCluster
  when: cluster_check.rc != 0
```

### Running on Wrong Host

```yaml
# BAD - API calls from managed node
- name: Create VM
  community.proxmox.proxmox_kvm:
    api_host: "{{ inventory_hostname }}"  # Calling itself
    # ...

# GOOD - Delegate API calls to localhost
- name: Create VM
  community.proxmox.proxmox_kvm:
    api_host: "{{ proxmox_api_host }}"
    # ...
  delegate_to: localhost
```

## Installing the Collection

```bash
# Install community.proxmox
uv run ansible-galaxy collection install community.proxmox

# Or via requirements.yml
cd ansible
uv run ansible-galaxy collection install -r requirements.yml
```

## Additional Resources

For detailed Proxmox automation patterns, consult:

- **`references/ceph-automation.md`** - CEPH storage deployment and OSD management
- **`references/cluster-automation.md`** - Proxmox cluster creation and node joining
- **`references/network-automation.md`** - VLAN-aware bridges and network configuration
- **`references/community-proxmox-plugin-index.md`** - Complete community.proxmox module reference

## Related Skills

- **ansible-fundamentals** - Core module selection patterns
- **ansible-idempotency** - Making CLI commands idempotent
- **ansible-error-handling** - Error recovery for cluster operations

Overview

This skill provides practical guidance and Ansible patterns for automating Proxmox VE using the community.proxmox collection while minimizing raw CLI usage. It shows when to prefer native Ansible modules, how to handle operations that require CLI, and recommended authentication and idempotency patterns. The content focuses on reliable, repeatable automation for VMs, clusters, Ceph, storage, and networking.

How this skill works

The skill inspects common Proxmox tasks and maps them to community.proxmox modules when available, falling back to CLI commands only when no module exists. It provides example playbook fragments, idempotency checks for CLI workflows, API token and root-password authentication patterns, and guidance for delegating API calls. It also documents cluster and Ceph workflows with checks to avoid destructive re-runs.

When to use it

  • Creating, cloning, or configuring VMs and templates with Ansible and Proxmox.
  • Managing users, groups, pools, ACLs, and storage via community.proxmox modules.
  • Forming or joining Proxmox clusters where no native module exists (pvecm).
  • Initializing or managing Ceph where complex CLI sequences are required (pveceph).
  • Configuring node network interfaces and VLAN-aware bridges via Ansible.

Best practices

  • Prefer community.proxmox modules over pvecm/pveum/pvesm/qm commands when modules exist.
  • Delegate API-based tasks to localhost instead of running modules on managed nodes.
  • Use API tokens for automation and limit root password use to local node operations.
  • Wrap CLI commands with idempotency checks (pvecm status, pveceph status) to avoid repeated changes.
  • Mark sensitive tasks no_log for secrets and use a secrets manager lookup for token secrets.

Example use cases

  • Create a VM from a template using community.proxmox.proxmox_kvm delegated to localhost.
  • Create and manage automation users and ACLs with community.proxmox.proxmox_user and proxmox_acl.
  • Form a new Proxmox cluster by checking pvecm status, creating on primary, and adding secondaries.
  • Initialize Ceph and create OSDs with pveceph commands wrapped in existence checks.
  • Configure VLAN-aware bridges using community.general.interfaces_file and notify network reload.

FAQ

When should I use CLI instead of modules?

Use CLI when no native module exists (cluster create/join, Ceph init/OSD workflows) and always add idempotency checks so tasks are safe to re-run.

How should I authenticate automation tasks?

Prefer API tokens stored in a secrets manager; use root@pam and root password only for local node operations that require node-local access.