
How to Set Up Ansible Inventories
Ansible inventories serve as the foundation for managing and organizing your infrastructure automation. They define which servers, devices, or cloud instances Ansible will target when executing playbooks, how to connect to them, and what variables apply to each host. Whether you’re managing a handful of development servers or orchestrating thousands of production machines across multiple cloud providers, setting up your inventories correctly determines the success of your entire automation strategy. This guide walks through creating static and dynamic inventories, organizing hosts into logical groups, handling complex variable hierarchies, and troubleshooting common inventory issues that trip up both newcomers and experienced practitioners.
How Ansible Inventories Work
An Ansible inventory acts as a database of managed nodes, containing hostnames, IP addresses, connection parameters, and variables that define how Ansible communicates with each target system. The inventory system operates on a hierarchical structure where hosts belong to groups, groups can contain other groups, and variables cascade down through this hierarchy with specific precedence rules.
When you run an Ansible playbook, the inventory gets parsed first, building an internal representation of your infrastructure. Ansible then applies variable precedence rules, resolving conflicts between host variables, group variables, and playbook variables. The connection plugins use inventory data to establish SSH connections, determine privilege escalation methods, and pass the correct authentication credentials.
Static inventories use INI or YAML files that you manually maintain, while dynamic inventories query external systems like cloud APIs, CMDB systems, or container orchestrators to build the host list at runtime. Both approaches can coexist, and Ansible automatically merges multiple inventory sources when you specify a directory containing multiple inventory files.
Basic Static Inventory Setup
The simplest inventory format uses INI-style syntax. Create a file called hosts
or inventory
in your project directory:
[webservers]
web1.example.com
web2.example.com
192.168.1.100
[databases]
db1.example.com ansible_host=10.0.1.50
db2.example.com ansible_host=10.0.1.51
[loadbalancers]
lb1.example.com ansible_port=2222 ansible_user=admin
[production:children]
webservers
databases
loadbalancers
This inventory defines three main groups plus a parent group that contains all production servers. The ansible_host
parameter overrides DNS resolution, useful when internal IPs differ from public hostnames. The ansible_port
and ansible_user
parameters customize connection settings per host.
For more complex scenarios, YAML format provides better readability and supports nested data structures:
all:
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
web3.example.com:
ansible_host: 192.168.1.103
http_port: 8080
databases:
hosts:
db1.example.com:
ansible_host: 10.0.1.50
mysql_replication_role: master
db2.example.com:
ansible_host: 10.0.1.51
mysql_replication_role: slave
vars:
mysql_port: 3306
backup_schedule: "0 2 * * *"
Test your inventory structure using the ansible-inventory command:
# List all hosts
ansible-inventory -i inventory --list
# Show specific group
ansible-inventory -i inventory --graph
# Verify host variables
ansible-inventory -i inventory --host web1.example.com
Advanced Group Organization and Variables
Effective inventory organization relies on logical grouping that reflects your infrastructure’s purpose, environment, and operational needs. Create groups based on functionality, geographic location, environment stage, or deployment patterns:
[frontend]
web[01:05].prod.example.com
[backend]
api[01:03].prod.example.com
worker[01:10].prod.example.com
[monitoring]
prometheus.prod.example.com
grafana.prod.example.com
# Geographic grouping
[us-east]
web01.prod.example.com
web02.prod.example.com
api01.prod.example.com
[eu-west]
web03.prod.example.com
web04.prod.example.com
api02.prod.example.com
# Environment grouping
[production:children]
frontend
backend
monitoring
[staging]
web-staging.example.com
api-staging.example.com
Ansible supports range patterns for hosts with numeric suffixes. The web[01:05].prod.example.com
notation expands to web01, web02, web03, web04, and web05. You can also use alphabetic ranges like web[a:e].example.com
.
Variable hierarchy becomes crucial in complex inventories. Create a group_vars
directory structure to organize variables:
inventory/
├── hosts
├── group_vars/
│ ├── all.yml
│ ├── production.yml
│ ├── webservers.yml
│ └── databases.yml
└── host_vars/
├── web1.example.com.yml
└── db1.example.com.yml
The group_vars/all.yml
file applies variables to every host:
# group_vars/all.yml
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
ssl_cert_path: /etc/ssl/certs
log_level: info
Group-specific variables override the global defaults:
# group_vars/webservers.yml
nginx_worker_processes: "{{ ansible_processor_vcpus }}"
nginx_worker_connections: 1024
ssl_protocols: "TLSv1.2 TLSv1.3"
# group_vars/databases.yml
mysql_innodb_buffer_pool_size: "{{ (ansible_memtotal_mb * 0.7) | int }}M"
mysql_max_connections: 200
backup_retention_days: 30
Dynamic Inventory Configuration
Dynamic inventories eliminate manual host management by querying external systems. Most cloud providers offer official Ansible inventory plugins. Configure AWS EC2 dynamic inventory by creating aws_ec2.yml
:
plugin: aws_ec2
regions:
- us-east-1
- us-west-2
filters:
instance-state-name: running
hostnames:
- dns-name
- private-ip-address
compose:
ansible_host: private_ip_address
groups:
webservers: "'web' in tags.Role"
databases: "'db' in tags.Role"
production: "tags.Environment == 'prod'"
staging: "tags.Environment == 'staging'"
keyed_groups:
- key: tags.Environment
prefix: env
- key: instance_type
prefix: type
This configuration creates groups based on EC2 tags and instance metadata. The compose
section sets ansible_host
to use private IP addresses for internal communication. The keyed_groups
feature automatically creates groups like env_prod
, env_staging
, type_t3_medium
.
For Google Cloud Platform, use the gcp_compute
plugin:
plugin: gcp_compute
projects:
- my-gcp-project
zones:
- us-central1-a
- us-central1-b
filters:
- status = RUNNING
auth_kind: serviceaccount
service_account_file: /path/to/service-account.json
hostnames:
- name
compose:
ansible_host: networkInterfaces[0].accessConfigs[0].natIP
groups:
webservers: "'webserver' in labels.role"
databases: "'database' in labels.role"
Azure dynamic inventory uses similar patterns with the azure_rm
plugin. Each cloud plugin supports different filtering options and metadata fields, so consult the official documentation for platform-specific features.
Test dynamic inventory configuration:
# Test AWS EC2 inventory
ansible-inventory -i aws_ec2.yml --graph
# List all hosts with variables
ansible-inventory -i aws_ec2.yml --list
# Verify specific host details
ansible-inventory -i aws_ec2.yml --host i-1234567890abcdef0
Real-World Use Cases and Examples
A typical web application deployment uses multiple inventory patterns. Consider this structure for a multi-tier application across development, staging, and production environments:
inventory/
├── production/
│ ├── hosts.yml
│ ├── aws_ec2.yml
│ └── group_vars/
├── staging/
│ ├── hosts.yml
│ └── group_vars/
└── development/
├── vagrant.yml
└── group_vars/
The production inventory combines static and dynamic sources. Static entries handle on-premises servers, while dynamic inventory manages cloud instances:
# inventory/production/hosts.yml
all:
children:
onpremise:
children:
legacy_systems:
hosts:
mainframe.internal.com:
ansible_user: ansible
ansible_become: yes
physical_databases:
hosts:
db-primary.internal.com:
mysql_replication_role: master
db-replica.internal.com:
mysql_replication_role: slave
cloud:
children:
# Groups populated by aws_ec2.yml dynamic inventory
A microservices environment might organize inventory by service ownership and deployment strategy:
# Kubernetes-based microservices inventory
plugin: k8s
connections:
- host: https://k8s-api.example.com
compose:
ansible_host: status.podIP
service_name: metadata.labels['app.kubernetes.io/name']
version: metadata.labels['app.kubernetes.io/version']
groups:
user_service: "service_name == 'user-service'"
payment_service: "service_name == 'payment-service'"
frontend: "service_name == 'frontend'"
canary_deployments: "'canary' in metadata.labels"
For organizations using configuration management alongside Ansible, you might integrate with existing CMDB systems:
#!/usr/bin/env python3
# custom_cmdb_inventory.py
import json
import requests
import sys
def get_inventory():
# Query CMDB API
response = requests.get('https://cmdb.company.com/api/servers')
servers = response.json()
inventory = {
'_meta': {
'hostvars': {}
}
}
for server in servers:
# Organize by application and environment
app_env_group = f"{server['application']}_{server['environment']}"
if app_env_group not in inventory:
inventory[app_env_group] = {'hosts': []}
inventory[app_env_group]['hosts'].append(server['hostname'])
# Add host variables
inventory['_meta']['hostvars'][server['hostname']] = {
'ansible_host': server['ip_address'],
'application': server['application'],
'environment': server['environment'],
'owner': server['owner'],
'maintenance_window': server['maintenance_window']
}
return inventory
if __name__ == '__main__':
print(json.dumps(get_inventory(), indent=2))
Comparison with Alternative Approaches
Different inventory management strategies suit different organizational needs and infrastructure scales:
Approach | Best For | Pros | Cons | Maintenance Overhead |
---|---|---|---|---|
Static INI Files | Small, stable environments | Simple, version controlled, fast parsing | Manual updates, limited structure | Low |
Static YAML Files | Complex variable hierarchies | Structured data, readable, version controlled | Manual updates, slower parsing | Low |
Cloud Dynamic Inventory | Auto-scaling cloud infrastructure | Automatic updates, tag-based grouping | API dependencies, slower execution | Medium |
Custom Scripts | Integration with existing CMDB | Flexible, existing data sources | Custom maintenance, error handling | High |
Hybrid Approach | Mixed environments | Flexibility, gradual migration | Complexity, multiple sources | Medium |
Performance characteristics vary significantly between inventory types. Static inventories parse in milliseconds, while dynamic inventories might take several seconds for large cloud deployments. Here’s a benchmark from a real environment managing 500 hosts:
Inventory Type | Parse Time | Memory Usage | API Calls | Cache TTL |
---|---|---|---|---|
Static INI | 0.1s | 15MB | 0 | N/A |
AWS EC2 Plugin | 3.2s | 45MB | 12 | 300s |
Custom CMDB Script | 1.8s | 25MB | 1 | 600s |
Kubernetes Plugin | 2.1s | 35MB | 3 | 300s |
Best Practices and Common Pitfalls
Successful inventory management requires consistent naming conventions, proper variable organization, and careful attention to security. Establish naming standards early:
# Good: Consistent, descriptive naming
[web_production_useast1]
web01.prod.useast1.company.com
web02.prod.useast1.company.com
[db_production_useast1]
db01.prod.useast1.company.com
# Bad: Inconsistent, unclear naming
[webservers]
server1
web-box-2
production-web-3.company.com
Keep sensitive data out of inventory files. Use Ansible Vault for passwords, API keys, and certificates:
# group_vars/databases.yml
mysql_root_password: "{{ vault_mysql_root_password }}"
ssl_private_key: "{{ vault_ssl_private_key }}"
# Encrypt sensitive variables
ansible-vault encrypt group_vars/databases/vault.yml
Common pitfalls include variable precedence confusion, circular group dependencies, and performance issues with large dynamic inventories. Variable precedence follows this order (highest to lowest):
- Extra vars (command line -e)
- Task vars
- Block vars
- Role and include vars
- Play vars
- Host vars
- Group vars (most specific group)
- Group vars (less specific groups)
- Group vars (all)
Avoid circular group dependencies that cause Ansible to fail:
# This creates a circular dependency - DON'T DO THIS
[webservers:children]
frontend
[frontend:children]
webservers # Circular reference
Optimize dynamic inventory performance by implementing caching and filtering:
# aws_ec2.yml with performance optimizations
plugin: aws_ec2
regions:
- us-east-1 # Limit regions
filters:
instance-state-name: running
"tag:Managed": "ansible" # Filter by management tag
cache: true
cache_plugin: jsonfile
cache_timeout: 300
cache_connection: /tmp/ansible_inventory_cache
Use inventory plugins’ built-in caching mechanisms rather than implementing custom caching logic. Most plugins support cache configuration through the main Ansible configuration file.
For large environments, consider splitting inventory into multiple files and using inventory directories. Ansible automatically merges all inventory sources in a directory:
inventory/
├── 01-static-infrastructure
├── 02-aws-ec2.yml
├── 03-gcp-compute.yml
└── 04-kubernetes.yml
Test inventory changes in isolated environments before applying to production. Use the --check
and --diff
flags with ansible-playbook to validate changes, and maintain separate inventory files for each environment to prevent accidental cross-environment deployments.
Monitor inventory performance and API usage, especially with dynamic inventories. Set up alerts for inventory parsing failures and implement fallback mechanisms for critical automation workflows. Consider implementing inventory validation tests as part of your CI/CD pipeline to catch configuration errors before they affect production systems.
For additional information on inventory plugins and advanced configuration options, consult the official Ansible inventory documentation and the inventory plugins reference.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.