Ansible

Setup Ansible Dynamic Inventory for Auto Host Discovery

Static inventory files work until you have more than a handful of servers. Once VMs spin up and down in Proxmox, instances launch in AWS, or containers scale in Kubernetes, maintaining a text file of hostnames becomes a losing game. Dynamic inventory solves this by querying your infrastructure directly and building the host list at runtime.

Original content from computingforgeeks.com - post 166052

This guide covers three approaches: the Proxmox inventory plugin (tested against a real 3-node cluster), a Python script-based inventory for custom sources, and the AWS EC2 plugin configuration. We test on Rocky Linux 10.1 with Ansible 13.5.0 (ansible-core 2.20.4) and the community.proxmox 1.6.0 collection. For static inventory basics, see the Ansible inventory management guide.

Tested April 2026 on Rocky Linux 10.1, ansible-core 2.20.4, community.proxmox 1.6.0, Proxmox VE 8.x cluster

Prerequisites

You need:

  • Ansible installed on a control node (Install Ansible on Rocky Linux 10 / Ubuntu 24.04)
  • For Proxmox: API access to a Proxmox VE cluster with an API token
  • Python proxmoxer library: pip3 install proxmoxer requests
  • The community.proxmox collection: ansible-galaxy collection install community.proxmox

How Dynamic Inventory Works

Instead of a static inventory.ini file, Ansible accepts an inventory source that returns JSON in a specific format. This source can be a YAML plugin config file or an executable script. At runtime, Ansible calls the source, gets back a list of hosts with their groups and variables, and proceeds as if you’d written it all by hand.

The key difference: static inventory is a snapshot that goes stale. Dynamic inventory reflects reality every time you run a playbook.

Proxmox Dynamic Inventory Plugin

The community.proxmox.proxmox plugin queries the Proxmox API and discovers all VMs and containers across your cluster. It auto-groups hosts by node, type (QEMU/LXC), and running status.

Create an API Token

Generate a dedicated API token on your Proxmox node. This avoids using your root password in config files:

pveum user token add root@pam ansible-inventory --privsep=0

The output provides the token ID and secret value:

┌──────────────┬──────────────────────────────────────┐
│ key          │ value                                │
╞══════════════╪══════════════════════════════════════╡
│ full-tokenid │ root@pam!ansible-inventory           │
├──────────────┼──────────────────────────────────────┤
│ value        │ 6c4de701-024e-446d-aa3b-xxxxxxxxxxxx │
└──────────────┴──────────────────────────────────────┘

Save the token secret. You won’t be able to retrieve it again from Proxmox. The --privsep=0 flag gives the token the same permissions as the user (root in this case). For production, create a dedicated user with read-only access to /vms instead.

Configure the Inventory Plugin

Create a file ending in .proxmox.yml or .proxmox.yaml. The filename suffix is how Ansible recognizes it as a Proxmox inventory source:

vi inventory.proxmox.yml

Add the plugin configuration:

---
plugin: community.proxmox.proxmox
url: https://10.0.1.3:8006
user: root@pam
token_id: ansible-inventory
token_secret: 6c4de701-024e-446d-aa3b-xxxxxxxxxxxx
validate_certs: false
want_proxmox_nodes_ansible_host: false
want_facts: true

Set validate_certs: false if your Proxmox instance uses a self-signed certificate (common in homelab and internal environments). In production with proper certificates, set this to true.

Test the Discovery

Verify the plugin discovers your VMs with ansible-inventory --graph:

ansible-inventory -i inventory.proxmox.yml --graph

The output shows every VM across the cluster, automatically grouped by Proxmox node, type, and status:

@all:
  |--@proxmox_all_qemu:
  |  |--Rocky-10-Enock
  |  |--Debian-13-Enock
  |  |--ansible-controller
  |  |--ansible-rocky-managed
  |  |--ansible-ubuntu-managed
  |--@proxmox_all_running:
  |  |--Rocky-10-Enock
  |  |--Debian-13-Enock
  |  |--ansible-controller
  |  |--ansible-rocky-managed
  |  |--ansible-ubuntu-managed
  |--@proxmox_nodes:
  |  |--pve01
  |  |--pve02
  |  |--pve03
  |--@proxmox_pve02_qemu:
  |  |--Rocky-10-Enock
  |  |--ansible-controller
  |  |--ansible-rocky-managed
  |--@proxmox_pve01_qemu:
  |  |--ansible-ubuntu-managed

Without writing a single line of inventory, Ansible now knows about every VM in the cluster. VMs that get created or destroyed are automatically included or excluded on the next run.

Custom Groups with the groups Parameter

The auto-generated groups (proxmox_all_qemu, proxmox_pve01_qemu) are useful but generic. The groups parameter lets you create meaningful groups based on VM names or other Proxmox attributes:

---
plugin: community.proxmox.proxmox
url: https://10.0.1.3:8006
user: root@pam
token_id: ansible-inventory
token_secret: 6c4de701-024e-446d-aa3b-xxxxxxxxxxxx
validate_certs: false
want_facts: true

# Custom groups based on VM name patterns
groups:
  ansible_lab: "'ansible' in proxmox_name"
  rocky_vms: "'Rocky' in proxmox_name or 'rocky' in proxmox_name"
  ubuntu_vms: "'ubuntu' in proxmox_name or 'u2604' in proxmox_name"

# Set ansible_host from cloud-init IP config
compose:
  ansible_host: proxmox_ipconfig0.ip | default(omit)

Now the inventory includes your custom groups alongside the auto-generated ones:

  |--@ansible_lab:
  |  |--ansible-controller
  |  |--ansible-rocky-managed
  |  |--ansible-ubuntu-managed
  |--@rocky_vms:
  |  |--Rocky-10-Enock
  |  |--Rocky-10
  |  |--ansible-rocky-managed
  |--@ubuntu_vms:
  |  |--ansible-ubuntu-managed
  |  |--u2604-ansible
  |  |--u2604-compose

The compose section sets ansible_host from the cloud-init IP configuration stored in Proxmox. This means Ansible knows how to reach each VM without you specifying IPs anywhere.

Proxmox dynamic inventory discovering VMs across cluster nodes
Dynamic inventory auto-discovering VMs across a 3-node Proxmox cluster with custom group assignments

Script-Based Dynamic Inventory

When no official plugin exists for your infrastructure (a custom CMDB, an internal API, a spreadsheet), you can write a Python script that Ansible calls as an inventory source. The script must support two flags: --list (return all hosts and groups) and --host <hostname> (return vars for one host).

The Inventory Script Format

Create an executable Python script:

vi dynamic_inventory.py

Add the inventory logic:

#!/usr/bin/env python3
"""Script-based dynamic inventory example."""
import json, sys

def get_inventory():
    return {
        "webservers": {
            "hosts": ["web01", "web02"],
            "vars": {"http_port": 80, "deploy_user": "www-data"}
        },
        "databases": {
            "hosts": ["db01"],
            "vars": {"db_port": 5432, "backup_schedule": "daily"}
        },
        "_meta": {
            "hostvars": {
                "web01": {"ansible_host": "10.0.1.10", "server_role": "primary"},
                "web02": {"ansible_host": "10.0.1.11", "server_role": "secondary"},
                "db01": {"ansible_host": "10.0.1.20", "db_engine": "postgresql"}
            }
        }
    }

if __name__ == "__main__":
    if len(sys.argv) == 2 and sys.argv[1] == "--list":
        print(json.dumps(get_inventory(), indent=2))
    elif len(sys.argv) == 3 and sys.argv[1] == "--host":
        hostvars = get_inventory()["_meta"]["hostvars"]
        print(json.dumps(hostvars.get(sys.argv[2], {}), indent=2))
    else:
        print(json.dumps({"_meta": {"hostvars": {}}}))

Make it executable and test:

chmod +x dynamic_inventory.py
ansible-inventory -i dynamic_inventory.py --graph

Ansible discovers the hosts and groups from the script output:

@all:
  |--@ungrouped:
  |--@webservers:
  |  |--web01
  |  |--web02
  |--@databases:
  |  |--db01

The _meta section with hostvars is critical. Without it, Ansible would call --host for every single host individually, which is slow. Including _meta in the --list response lets Ansible get all host variables in a single call.

In production, replace the hardcoded dictionary with an API call to your CMDB, cloud provider, or service registry. The JSON format stays the same.

AWS EC2 Dynamic Inventory

The amazon.aws.aws_ec2 plugin discovers EC2 instances. Install the collection first:

ansible-galaxy collection install amazon.aws
pip3 install boto3 botocore

Create a file ending in .aws_ec2.yml:

---
plugin: amazon.aws.aws_ec2
regions:
  - eu-west-1
filters:
  instance-state-name: running
  "tag:Environment": production

keyed_groups:
  - key: tags.Role
    prefix: role
  - key: placement.availability_zone
    prefix: az

compose:
  ansible_host: private_ip_address

The keyed_groups parameter creates Ansible groups from EC2 tags. An instance tagged Role: webserver in eu-west-1a would appear in both role_webserver and az_eu_west_1a groups. The compose section sets ansible_host to the private IP, which works when your Ansible controller runs inside the same VPC.

Combining Static and Dynamic Inventory

Ansible can use multiple inventory sources simultaneously. Point it at a directory containing both static and dynamic sources:

mkdir -p inventory/
cp inventory.ini inventory/static.ini
cp inventory.proxmox.yml inventory/
ansible-inventory -i inventory/ --graph

Or specify multiple sources on the command line:

ansible-playbook -i inventory.ini -i inventory.proxmox.yml deploy.yml

Hosts from all sources are merged. If the same hostname appears in both static and dynamic inventories, variables from both sources are combined using the standard variable precedence rules.

Debugging Dynamic Inventory

Three commands for troubleshooting inventory issues:

Show the full group tree:

ansible-inventory -i inventory.proxmox.yml --graph

Dump all variables for a specific host:

ansible-inventory -i inventory.proxmox.yml --host ansible-controller

Export the complete inventory as JSON (useful for comparing expected vs actual):

ansible-inventory -i inventory.proxmox.yml --list --export

If a host isn’t appearing, check that the plugin can reach the API endpoint, the token has sufficient permissions, and the host meets any filter criteria you defined.

Quick Reference

Inventory TypeFile ConventionWhen to Use
Proxmox plugin*.proxmox.ymlProxmox VE clusters
AWS EC2 plugin*.aws_ec2.ymlAWS EC2 instances
GCP plugin*.gcp.ymlGoogle Compute Engine
Azure plugin*.azure_rm.ymlAzure VMs
ScriptExecutable fileCustom APIs, CMDBs
Static*.ini or *.ymlSmall, stable environments

For the complete Ansible learning path, see the Ansible Automation Guide. Dynamic inventory works especially well with Ansible roles and Jinja2 templates for fully automated infrastructure that adapts as hosts come and go. The Ansible + Proxmox integration guide covers VM provisioning alongside dynamic discovery.

Related Articles

Automation Migrate from Earthly to Dagger: Complete Guide Ansible Automated Logical Volume Manager (LVM) Management on Linux using Ansible Automation How To Install Jenkins on CentOS 8 / RHEL 8 Ansible Your First Ansible Playbook: Step-by-Step Guide

Leave a Comment

Press ESC to close