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 Dynamic Inventory: Complete Guide to AWS, Azure, GCP, and Custom Plugins

By Luca Berton · Published 2024-01-01 · Category: installation

Master Ansible dynamic inventory. Configure AWS EC2, Azure, GCP, Proxmox, VMware, and custom inventory plugins.

What Is Dynamic Inventory?

Static inventory files work when your infrastructure is fixed — a known list of servers that rarely changes. But in cloud environments where instances scale up and down, containers spawn and die, and infrastructure is ephemeral, you need inventory that discovers hosts automatically.

Dynamic inventory queries external sources (AWS, Azure, VMware, a CMDB, a database) and builds the host list at runtime. No manual updates needed.

See also: AAP 2.6 Cloud Automation: AWS, Azure, and GCP with Ansible

Inventory Plugins vs Scripts

Ansible supports two approaches:

FeatureInventory Plugin (recommended)Inventory Script (legacy)
FormatYAML configuration fileExecutable script (Python, Bash)
CachingBuilt-inManual
ComposableYes (combine with static)Limited
MaintainedActivelyDeprecated for most use cases
PerformanceOptimizedVaries
Always use inventory plugins unless you have a unique data source with no existing plugin.

AWS EC2 Dynamic Inventory

Setup

# Install AWS collection
ansible-galaxy collection install amazon.aws

# Install boto3
pip install boto3 botocore

# Configure AWS credentials
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
export AWS_DEFAULT_REGION="us-east-1"
# Or use ~/.aws/credentials

Inventory Configuration

# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2

# Regions to query
regions:
  - us-east-1
  - eu-west-1

# Filter instances
filters:
  instance-state-name: running
  "tag:Environment":
    - production
    - staging

# Group by tags, instance type, region
keyed_groups:
  - key: tags.Environment
    prefix: env
    separator: "_"
  - key: instance_type
    prefix: type
  - key: placement.region
    prefix: region
  - key: tags.Role
    prefix: role

# Set connection variables
compose:
  ansible_host: private_ip_address
  # Use public IP if no VPN:
  # ansible_host: public_ip_address | default(private_ip_address)
  ansible_user: "'ubuntu'"
  ansible_ssh_private_key_file: "'~/.ssh/aws-key.pem'"

# Create groups from tags
groups:
  webservers: tags.Role == 'web'
  databases: tags.Role == 'database'
  monitoring: tags.Role == 'monitoring'

# Caching (optional, speeds up repeated runs)
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/aws_inventory_cache
cache_timeout: 300  # 5 minutes

Test It

# List all discovered hosts
ansible-inventory -i inventory/aws_ec2.yml --graph

# Output:
# @all:
#   |--@env_production:
#   |  |--web-01
#   |  |--web-02
#   |  |--db-01
#   |--@env_staging:
#   |  |--staging-web-01
#   |--@role_web:
#   |  |--web-01
#   |  |--web-02
#   |--@type_t3_medium:
#   |  |--web-01
#   |  |--web-02

# Show all variables for a host
ansible-inventory -i inventory/aws_ec2.yml --host web-01

# Use in a playbook
ansible-playbook -i inventory/aws_ec2.yml site.yml

See also: Ansible for Cloud Automation: AWS, Azure, and GCP Complete Guide

Azure Dynamic Inventory

# Install Azure collection
ansible-galaxy collection install azure.azcollection
pip install azure-identity azure-mgmt-compute azure-mgmt-network azure-mgmt-resource
# inventory/azure_rm.yml
plugin: azure.azcollection.azure_rm

# Authentication (or use env vars / managed identity)
auth_source: auto

# Include only running VMs
include_vm_resource_groups:
  - production-rg
  - staging-rg

# Conditional groups
conditional_groups:
  webservers: "'web' in tags.Role | default('')"
  databases: "'db' in tags.Role | default('')"

# Keyed groups
keyed_groups:
  - key: tags.Environment | default('untagged')
    prefix: env
  - key: location
    prefix: region
  - key: os_profile.system | default('linux')
    prefix: os

# Connection settings
compose:
  ansible_host: private_ipv4_addresses[0] | default(public_ipv4_addresses[0])
  ansible_user: "'azureadmin'"

# Exclude deallocated VMs
exclude_host_filters:
  - powerstate != "running"

# Caching
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/azure_inventory_cache
cache_timeout: 600

GCP Dynamic Inventory

ansible-galaxy collection install google.cloud
pip install google-auth requests
# inventory/gcp.yml
plugin: google.cloud.gcp_compute

# Authentication
auth_kind: serviceaccount
service_account_file: /path/to/service-account.json

# Projects to query
projects:
  - my-gcp-project

# Zones
zones:
  - us-central1-a
  - us-central1-b
  - europe-west1-b

# Filters
filters:
  - status = RUNNING
  - labels.environment = production

# Grouping
keyed_groups:
  - key: labels.role
    prefix: role
  - key: zone
    prefix: zone
  - key: machine_type | basename
    prefix: type

compose:
  ansible_host: networkInterfaces[0].networkIP
  ansible_user: "'ansible'"

groups:
  webservers: labels.role == "web"
  databases: labels.role == "database"

cache: true
cache_plugin: jsonfile
cache_connection: /tmp/gcp_inventory_cache
cache_timeout: 300

See also: Ansible Terraform Integration: Orchestrate Infrastructure and Configuration Together

VMware Dynamic Inventory

ansible-galaxy collection install community.vmware
pip install pyvmomi
# inventory/vmware.yml
plugin: community.vmware.vmware_vm_inventory

hostname: vcenter.company.com
username: "ansible@vsphere.local"
password: "{{ vault_vcenter_password }}"
validate_certs: false

# Only powered-on VMs
with_nested_properties: true
resources:
  - datacenter:
      - DC1
    resources:
      - compute_resource:
          - Cluster1

filters:
  - runtime.powerState == "poweredOn"

keyed_groups:
  - key: config.guestId
    prefix: os
  - key: guest.net[0].network | default('unknown')
    prefix: network

compose:
  ansible_host: guest.ipAddress
  ansible_user: "'ansible'"

properties:
  - config.name
  - config.guestId
  - guest.ipAddress
  - runtime.powerState
  - config.hardware.memoryMB
  - config.hardware.numCPU

Combining Multiple Inventory Sources

Create an inventory directory with multiple sources:

inventory/
├── 01-static.yml          # Static hosts (network devices, etc.)
├── 02-aws_ec2.yml         # AWS dynamic inventory
├── 03-azure_rm.yml        # Azure dynamic inventory
├── group_vars/
│   ├── all.yml            # Variables for all hosts
│   ├── webservers.yml     # Variables for web group
│   └── databases.yml      # Variables for db group
└── host_vars/
    └── special-host.yml   # Host-specific variables
# Ansible reads ALL files in the directory
ansible-playbook -i inventory/ site.yml

# Or set in ansible.cfg
[defaults]
inventory = ./inventory/

Custom Dynamic Inventory Script

When no plugin exists for your data source, write a custom script:

#!/usr/bin/env python3
"""Custom dynamic inventory from CMDB API."""
import json
import argparse
import requests

CMDB_URL = "https://cmdb.company.com/api/v1"
API_KEY = "your-api-key"

def get_inventory():
    """Fetch hosts from CMDB and build Ansible inventory."""
    headers = {"Authorization": f"Bearer {API_KEY}"}
    hosts = requests.get(f"{CMDB_URL}/hosts", headers=headers).json()

    inventory = {
        "_meta": {"hostvars": {}},
        "all": {"children": []}
    }

    groups = {}
    for host in hosts:
        hostname = host["fqdn"]

        # Add to groups
        for group in host.get("groups", []):
            if group not in groups:
                groups[group] = {"hosts": []}
                inventory["all"]["children"].append(group)
            groups[group]["hosts"].append(hostname)

        # Host variables
        inventory["_meta"]["hostvars"][hostname] = {
            "ansible_host": host["ip_address"],
            "ansible_user": host.get("ssh_user", "ansible"),
            "os_type": host.get("os", "linux"),
            "datacenter": host.get("datacenter", "unknown"),
            "environment": host.get("environment", "unknown"),
        }

    inventory.update(groups)
    return inventory

def get_host(hostname):
    """Fetch single host variables."""
    headers = {"Authorization": f"Bearer {API_KEY}"}
    host = requests.get(f"{CMDB_URL}/hosts/{hostname}", headers=headers).json()
    return {
        "ansible_host": host["ip_address"],
        "ansible_user": host.get("ssh_user", "ansible"),
    }

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--list", action="store_true")
    parser.add_argument("--host", type=str)
    args = parser.parse_args()

    if args.list:
        print(json.dumps(get_inventory(), indent=2))
    elif args.host:
        print(json.dumps(get_host(args.host), indent=2))
# Make executable
chmod +x cmdb_inventory.py

# Test
./cmdb_inventory.py --list | jq

# Use
ansible-playbook -i cmdb_inventory.py site.yml

Inventory Plugin Development

For a reusable, cacheable plugin:

# plugins/inventory/cmdb.py
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable

DOCUMENTATION = """
    name: cmdb
    plugin_type: inventory
    short_description: CMDB dynamic inventory
    options:
        api_url:
            description: CMDB API endpoint
            required: true
        api_key:
            description: API authentication key
            required: true
            env:
                - name: CMDB_API_KEY
"""

class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
    NAME = "cmdb"

    def verify_file(self, path):
        return super().verify_file(path) and path.endswith(("cmdb.yml", "cmdb.yaml"))

    def parse(self, inventory, loader, path, cache=True):
        super().parse(inventory, loader, path, cache)
        self._read_config_data(path)

        api_url = self.get_option("api_url")
        api_key = self.get_option("api_key")

        # Fetch and populate inventory
        # self.inventory.add_host(hostname)
        # self.inventory.add_group(group)
        # self.inventory.add_child(group, hostname)
        # self.inventory.set_variable(hostname, key, value)

Best Practices

Use Caching

# In your inventory plugin config
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/ansible_inventory_cache
cache_timeout: 300

# Clear cache when needed
ansible-inventory -i inventory/ --flush-cache

Tag Everything

Dynamic inventory grouping relies on tags/labels. Establish a tagging standard:

# Required tags for all cloud resources
tags:
  Environment: production    # Groups: env_production
  Role: web                  # Groups: role_web
  Team: platform             # Groups: team_platform
  Application: api           # Groups: app_api
  CostCenter: engineering    # For billing

Use Compose for Connection Variables

compose:
  # Choose IP based on network access
  ansible_host: >-
    private_ip_address if 'vpn' in group_names
    else public_ip_address | default(private_ip_address)

  # Set user based on OS
  ansible_user: >-
    'ec2-user' if image_id.startswith('ami-amazon')
    else 'ubuntu' if image_id.startswith('ami-ubuntu')
    else 'centos'

FAQ

How does Ansible dynamic inventory work?

Ansible queries an external data source (cloud API, CMDB, database) at runtime to build the list of hosts and their variables. Instead of maintaining a static file, the inventory is generated dynamically every time you run a playbook.

Can I mix static and dynamic inventory?

Yes. Put both static YAML files and dynamic inventory plugin configs in the same directory. Ansible merges them automatically. Static inventory is great for network devices, bastion hosts, and other fixed infrastructure alongside dynamic cloud hosts.

How do I debug dynamic inventory issues?

Use ansible-inventory -i your_inventory.yml --list to see the full generated inventory as JSON. Add --graph for a tree view. Check ansible-inventory -i your_inventory.yml --host hostname for specific host variables.

Which inventory plugin should I use for AWS?

Use amazon.aws.aws_ec2 (the collection-based plugin). The older aws_ec2 plugin from community.aws is deprecated. Always use the FQCN: plugin: amazon.aws.aws_ec2.

How do I filter hosts in dynamic inventory?

Use the filters parameter in your plugin config. For AWS, filters match EC2 API filters. For all plugins, keyed_groups with Jinja2 expressions let you create groups from any host attribute, and groups with conditions include/exclude hosts.

Conclusion

Dynamic inventory is essential for cloud-native Ansible automation. Use inventory plugins (not scripts) for AWS, Azure, GCP, VMware, and Proxmox. Tag your infrastructure consistently, use compose for connection variables, enable caching for performance, and combine multiple inventory sources in a single directory. When no plugin exists for your data source, write a custom script or develop a reusable inventory plugin.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home