Docs / Automation & IaC / Build Ansible Dynamic Inventory for Cloud Infrastructure

Build Ansible Dynamic Inventory for Cloud Infrastructure

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 222 views · 4 min read

Static inventory files become unmanageable as your infrastructure grows and changes. Dynamic inventory scripts and plugins query your cloud provider's API to automatically discover servers, their IPs, and metadata. This guide covers building and using dynamic inventory for Ansible with cloud providers and custom APIs.

Why Dynamic Inventory?

  • Auto-discovery: New servers appear automatically — no manual inventory updates
  • Accurate: Always reflects the current state of your infrastructure
  • Metadata as groups: Automatically group servers by tags, regions, or roles
  • Scalable: Works whether you have 5 or 5,000 servers

Hetzner Cloud Dynamic Inventory

# Install the Hetzner collection
ansible-galaxy collection install hetzner.hcloud

# inventory/hcloud.yml
plugin: hetzner.hcloud.hcloud
token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"

# Group servers by labels
keyed_groups:
  - key: labels.environment
    prefix: env
    separator: "_"
  - key: labels.role
    prefix: role
    separator: "_"
  - key: location
    prefix: location
    separator: "_"

# Filter servers
filters:
  - labels.managed_by == "ansible"

# Set connection variables
compose:
  ansible_host: ipv4_address
  ansible_user: "'deploy'"

# Cache for performance
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/ansible-inventory-cache
cache_timeout: 300

# Test it
# HCLOUD_TOKEN=your-token ansible-inventory -i inventory/hcloud.yml --list
# HCLOUD_TOKEN=your-token ansible-inventory -i inventory/hcloud.yml --graph

AWS EC2 Dynamic Inventory

# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
  - us-west-2

# Filter by tags
filters:
  tag:ManagedBy: ansible
  instance-state-name: running

# Group by tags and attributes
keyed_groups:
  - key: tags.Environment
    prefix: env
  - key: tags.Role
    prefix: role
  - key: placement.availability_zone
    prefix: az
  - key: instance_type
    prefix: type

# Set host variables
compose:
  ansible_host: public_ip_address | default(private_ip_address)
  ansible_user: "'ubuntu'"

# Use Name tag as hostname
hostnames:
  - tag:Name
  - private-ip-address

# Cache
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/aws-inventory-cache
cache_timeout: 600

Custom Dynamic Inventory Script

#!/usr/bin/env python3
# inventory/custom_inventory.py
"""
Custom dynamic inventory script for internal API.
Usage: ansible-playbook -i inventory/custom_inventory.py playbook.yml
"""

import json
import sys
import requests
import os
from argparse import ArgumentParser


def get_inventory():
    """Fetch server inventory from internal API."""
    api_url = os.environ.get('INVENTORY_API_URL', 'https://api.example.com/servers')
    api_token = os.environ.get('INVENTORY_API_TOKEN', '')

    headers = {'Authorization': f'Bearer {api_token}'}
    response = requests.get(api_url, headers=headers)
    servers = response.json()

    inventory = {
        '_meta': {'hostvars': {}},
        'all': {'children': ['webservers', 'databases', 'monitoring']},
        'webservers': {'hosts': []},
        'databases': {'hosts': []},
        'monitoring': {'hosts': []},
    }

    for server in servers:
        hostname = server['hostname']
        role = server.get('role', 'webservers')
        env = server.get('environment', 'production')

        # Add to role group
        if role not in inventory:
            inventory[role] = {'hosts': []}
            inventory['all']['children'].append(role)
        inventory[role]['hosts'].append(hostname)

        # Add to environment group
        env_group = f'env_{env}'
        if env_group not in inventory:
            inventory[env_group] = {'hosts': []}
            inventory['all']['children'].append(env_group)
        inventory[env_group]['hosts'].append(hostname)

        # Set host variables
        inventory['_meta']['hostvars'][hostname] = {
            'ansible_host': server['ip_address'],
            'ansible_user': server.get('ssh_user', 'deploy'),
            'server_id': server['id'],
            'datacenter': server.get('datacenter', 'us-east'),
            'os': server.get('os', 'ubuntu-24.04'),
        }

    return inventory


def get_host(hostname):
    """Get variables for a specific host."""
    inventory = get_inventory()
    return inventory['_meta']['hostvars'].get(hostname, {})


def main():
    parser = 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))
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == '__main__':
    main()
# Make executable and test
chmod +x inventory/custom_inventory.py
./inventory/custom_inventory.py --list | python -m json.tool

# Use with Ansible
ansible-playbook -i inventory/custom_inventory.py playbook.yml

Combining Static and Dynamic Inventory

# Use a directory as inventory source — Ansible merges all files
inventory/
├── static_hosts.yml    # Static entries (network devices, etc.)
├── hcloud.yml          # Hetzner Cloud dynamic inventory
├── aws_ec2.yml         # AWS dynamic inventory
└── custom_inventory.py # Custom API inventory

# ansible.cfg
[defaults]
inventory = ./inventory

# Ansible merges all sources automatically
ansible-inventory --list  # Shows combined inventory

Inventory Plugins vs Scripts

FeatureInventory Plugin (YAML)Inventory Script (Python)
CachingBuilt-inManual implementation
ConfigurationYAML fileEnvironment variables
MaintenanceCommunity-maintainedYou maintain it
FlexibilityProvider-specificUnlimited — any API
Best forStandard cloud providersCustom/internal systems

Best Practices

  • Enable caching: Dynamic inventory queries APIs on every run — cache to avoid rate limits and latency
  • Use keyed_groups: Automatically group servers by tags, regions, and roles
  • Combine static and dynamic: Use an inventory directory for mixed environments
  • Tag your servers: Good tagging in your cloud provider = good Ansible groups automatically
  • Set reasonable cache timeouts: 5-10 minutes for development, 30-60 minutes for stable production
  • Test with ansible-inventory --graph to visualize your inventory structure

Was this article helpful?