Inventory Design: Static INI/YAML, Group Hierarchy & Variables

Master Ansible inventory design, human-meaningful aliases, nested child groups, group_vars and host_vars directory inheritance, and dynamic cloud inventory plugins.

beginner 25 min lesson hands-on task included

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

INVENTORY HIERARCHY & VARIABLE SCOPING (group_vars / host_vars) [all] GROUP — INHERITED BY ALL HOSTS group_vars/all.yml [webservers] GROUP group_vars/webservers.yml http_port: 80 · max_clients: 200 web1.acme.com ansible_host=10.0.1.10 host_vars/web1.acme.com.yml (override http_port: 8080) web2.acme.com ansible_host=10.0.1.11 Inherits webservers group_vars [dbservers:children] NESTED GROUP Child Groups: [db_primary], [db_replica] db-master-01 (db_primary) ansible_host=10.0.2.20 db_role: primary · max_connections: 500 db-replica-01 (db_replica) ansible_host=10.0.2.21 db_role: replica · read_only: yes RED HAT BEST PRACTICE: USE MEANINGFUL INVENTORY NAMES Never use raw IP addresses as host identifiers! Use human-meaningful aliases (e.g., `web1 ansible_host=10.0.1.10`) for clean logging and targeted play execution.
Ansible Inventory Hierarchy: Global [all] group variables inherited down through specific host groups and overridden by host_vars.

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 NameDescriptionProduction Example
ansible_hostTarget IPv4/IPv6 address or SSH hostname10.0.1.10 or ec2-node.aws.com
ansible_portCustom target SSH port (default 22)2222
ansible_userRemote SSH login userubuntu, ec2-user, devops
ansible_ssh_private_key_filePath to host-specific SSH private key~/.ssh/prod_id_ed25519
ansible_python_interpreterExplicit path to target Python binary/usr/bin/python3
ansible_connectionTransport 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.