An Ansible Inventory is more than just a list of server IP addresses. It defines the organizational topology of your infrastructure, maps managed hosts to logical environment groups (web, database, staging, production), and establishes how variables cascade to individual servers.
Topic 1: INI vs. YAML Inventory Syntax Comparison
Ansible supports two primary file formats for static inventory definitions: INI and YAML.
1. INI Inventory Format (Classic & Compact)
INI is the traditional inventory format. It uses square brackets [group_name] to define host groups:
# INI Inventory: inventories/production/hosts.ini
# Standard Host Group
[webservers]
web1.acme.com ansible_host=10.0.1.10 ansible_user=ubuntu
web2.acme.com ansible_host=10.0.1.11 ansible_user=ubuntu
[dbservers]
db-master-01.acme.com ansible_host=10.0.2.20 ansible_user=redhat
db-replica-01.acme.com ansible_host=10.0.2.21 ansible_user=redhat
# Host Ranges (Expands to web-node-01 through web-node-20)
[appservers]
web-node-[01:20].acme.com ansible_host=10.0.3.[1:20]
# Nested Group Hierarchy (Group of Groups using :children)
[production:children]
webservers
dbservers
appservers
2. YAML Inventory Format (Structured & Schema-Validated)
YAML is preferred in modern GitOps pipelines because it mirrors Ansible playbook data structures:
# YAML Inventory: inventories/production/hosts.yml
all:
children:
production:
children:
webservers:
hosts:
web1.acme.com:
ansible_host: 10.0.1.10
ansible_user: ubuntu
web2.acme.com:
ansible_host: 10.0.1.11
ansible_user: ubuntu
dbservers:
hosts:
db-master-01.acme.com:
ansible_host: 10.0.2.20
ansible_user: redhat
db-replica-01.acme.com:
ansible_host: 10.0.2.21
ansible_user: redhat
Topic 2: Production Standard: Human-Meaningful Host Aliases
A frequent anti-pattern in beginner inventories is using raw IP addresses as host identifiers:
# BAD PRACTICE (Raw IP host names)
[webservers]
10.0.1.10
10.0.1.11
Why this is bad: When a playbook task runs, terminal logs display ok: [10.0.1.10]. In an outage or audit, engineers cannot immediately identify which server or role 10.0.1.10 represents.
# BEST PRACTICE (Human-Meaningful Host Aliases)
[webservers]
web1.prod.us-east.acme.com ansible_host=10.0.1.10
web2.prod.us-east.acme.com ansible_host=10.0.1.11
Why this is best: Execution logs display ok: [web1.prod.us-east.acme.com]. The host alias provides instant context while ansible_host routes the underlying SSH connection to the correct IP.
Topic 3: Structured Directory Layout (group_vars/ & host_vars/)
Embedding variables directly inside inventory files (using :vars sections in INI) creates massive, cluttered files that are difficult to review in Git pull requests.
The Red Hat Production Standard: Keep inventory files pure (containing host names and groups only) and move all variables into dedicated group_vars/ and host_vars/ subdirectories located alongside your inventory:
inventories/
└── production/
├── hosts.ini # Clean host groups without embedded inline vars
├── group_vars/
│ ├── all.yml # Variables applied to EVERY host in the inventory
│ ├── webservers.yml # Variables applied ONLY to hosts in [webservers]
│ └── dbservers.yml # Variables applied ONLY to hosts in [dbservers]
└── host_vars/
├── web1.prod.acme.com.yml # Specific overrides for host 'web1.prod.acme.com'
└── db-master-01.yml # Specific overrides for host 'db-master-01'
Example Variable Files:
# inventories/production/group_vars/all.yml (Global Defaults)
ntp_servers:
- time.google.com
- time.cloudflare.com
syslog_server: 10.0.0.250
organization_name: "Acme Fintech Corp"
# inventories/production/group_vars/webservers.yml (Group Specific)
http_port: 80
max_keepalive_requests: 100
nginx_worker_connections: 1024
# inventories/production/host_vars/web1.prod.acme.com.yml (Host Specific Override)
# Overrides group_vars/webservers.yml for this individual host only!
http_port: 8080
nginx_worker_connections: 4096
Topic 4: Built-In SSH Connection Variables (ansible_*)
Ansible recognizes special reserved inventory variables prefixed with ansible_* to control connection parameters:
| Variable Name | Description | Production Example |
|---|---|---|
ansible_host | Target IPv4/IPv6 address or SSH hostname | 10.0.1.10 or ec2-node.aws.com |
ansible_port | Custom target SSH port (default 22) | 2222 |
ansible_user | Remote SSH login user | ubuntu, ec2-user, devops |
ansible_ssh_private_key_file | Path to host-specific SSH private key | ~/.ssh/prod_id_ed25519 |
ansible_python_interpreter | Explicit path to target Python binary | /usr/bin/python3 |
ansible_connection | Transport plugin (smart, ssh, local, docker) | local for localhost execution |
Topic 5: Dynamic Cloud Inventories (AWS EC2 & GCP Plugins)
In cloud environments (AWS, GCP), servers scale elastically. Hand-editing static hosts.ini files whenever an autoscaling event occurs is impossible.
Dynamic Inventory Plugins query cloud provider APIs in real-time to build the inventory automatically.
Example: AWS EC2 Dynamic Inventory (aws_ec2.yml)
# inventories/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
- us-west-2
filters:
instance-state-name: running
keyed_groups:
# Automatically group instances by tag "Environment" (e.g., group 'env_production')
- key: tags.Environment
prefix: env
# Automatically group instances by tag "Role" (e.g., group 'role_web')
- key: tags.Role
prefix: role
# Group instances by AWS availability zone
- key: placement.availability_zone
prefix: az
Topic 6: Inspecting Inventories via CLI (ansible-inventory)
Ansible provides the ansible-inventory CLI tool to inspect, graph, and troubleshoot inventory variable resolution:
# 1. Print the complete inventory graph representation
ansible-inventory -i inventories/production/hosts.ini --graph
# 2. Inspect the fully resolved variable dictionary for a specific host
ansible-inventory -i inventories/production/hosts.ini --host web1.prod.acme.com
# 3. Export the entire inventory and variable tree as JSON
ansible-inventory -i inventories/production/hosts.ini --list
Common mistake: Putting variables in the inventory file itself rather than in group_vars/ and host_vars/. It works for a week, then nobody can tell which of three places set http_port, and a dynamic inventory plugin — which cannot carry your inline variables — becomes a rewrite instead of a swap.