The Inventory Model
The inventory is the list of machines Ansible can manage and the groups they belong to — the answer to "which hosts?" that every playbook and ad-hoc command needs before it can do anything. Ansible reads one or more inventory sources, parses them, and builds a single in-memory model of hosts and groups that the run targets against.
Two groups exist for free in every inventory, before you define a single one: all, which contains every host Ansible knows about, and ungrouped, which contains the hosts you placed in no other group. Understanding the model — hosts, groups, the implicit pair, and nesting — is what lets you point a play at exactly the machines you mean and no others.
Hosts and Groups
A host is a single managed node, identified by a name Ansible can resolve and reach over SSH — web1.larkspur.io, db1.larkspur.io. A group is a named set of hosts: web holds the web tier, db holds the database tier. Groups are the unit you target; you almost never name an individual host in a play, because the whole point is to act on a role rather than a machine.
A host can belong to many groups at once, and that is normal rather than an edge case. web1.larkspur.io is in web because of its role, in prod because of its environment, and in larkspur because of its estate. Each membership is a hook variables can hang off, which is how one host ends up with the web tier's nginx settings, production's stricter limits, and Larkspur's base packages all at once.
The Implicit Groups
Every inventory has all and ungrouped whether you write them or not. all contains every host in the model, so a play with hosts: all reaches the entire fleet — which is exactly what you want for a fleet-wide patch and exactly what you do not want when you meant one tier. ungrouped catches the hosts you never placed in a group you defined; a host listed at the top of an inventory with no group header lands there.
The all group is the single most common source of blast-radius mistakes. A pattern that resolves to all when you expected one group pushes a change to production database servers alongside the web hosts you were aiming at. The model is doing exactly what you asked; the danger is that "everything" is one careless pattern away, which is why a fleet-wide target deserves a deliberate pause every time.
Group-of-Groups
A group can contain other groups, not just hosts, through a children relationship. The larkspur group can be the parent of web and db, so every web and database host is also a member of larkspur without being listed there directly. Targeting larkspur then reaches the whole estate, and a variable set on larkspur flows down to every host in either child tier.
This is how environments get modeled cleanly. A prod parent with web and db children, mirrored by a staging parent over its own tiers, gives you a tree where variables and targeting apply at the level that makes sense — fleet-wide on the parent, role-specific on a child. The hierarchy is the structure your variables and patterns both lean on.
Inventory as the "Which Hosts" Layer
Playbooks describe what to do; inventory describes to whom. That split is deliberate, and it is the cleanest way to separate environments. The identical playbook that installs and configures the web tier runs against staging or production by swapping which inventory it reads — -i inventories/staging versus -i inventories/prod — with not a line of the playbook changed.
Because the inventory carries the hosts and their variables, the environment is entirely a property of which inventory you point at. There is no environment flag threaded through the play, no conditional that checks "are we in prod?" The play stays generic and the inventory carries the specifics, so the same reviewed, tested automation reaches every environment the same way.
One Source or Many
An inventory is not necessarily one file. It can be a single static file, a directory whose every file Ansible merges into one model, or a dynamic plugin that queries a cloud and returns the hosts that exist right now. You can combine them: a static core for the long-lived servers plus a cloud query for the autoscaled ones, all merged before the play begins.
Whatever the sources, Ansible reads them all into a single in-memory model before the first task runs. By the time a play targets web, the distinction between a host that came from a hand-written file and one that came from a cloud API has dissolved — both are just hosts in groups. The later topics in this chapter walk that path from a single file out to a merged, dynamic model.
A Terraform workspace separates environments by swapping which state file is active — switch workspaces and Terraform plans against a different recorded set of resources. The environment boundary lives in state, because state is how Terraform knows what exists.
An Ansible inventory separates environments by swapping which hosts and variables a playbook targets, with no state involved at all. The Ansible equivalent of "staging vs prod" is two inventories chosen by -i, not two states — the managed nodes are the truth, so there is nothing to record and nothing to drift.
- Targeting
allwhen you meant a single group, and pushing a change to every host in the fleet — production database servers included. The implicitallgroup is the most common blast-radius mistake, and it never announces itself. - Listing hosts at the top of an inventory in no group, then being surprised they match only
allandungrouped— a play targeted atwebsilently skips them because they were never put inweb. - Modeling environments by editing one inventory in place instead of keeping separate
stagingandprodinventories, so a careless run picks up whatever the file happened to contain and hits the wrong environment. - Assuming a host can only be in one group, then duplicating it or fighting the model — multi-group membership is normal, and it is exactly how
web1picks up web, prod, and Larkspur variables at once. - Running a play without first checking what a group resolves to, so a
childrenrelationship you forgot pulls in a tier you did not intend to touch.
- Keep one inventory per environment (
inventories/staging,inventories/prod) so the environment is chosen by-iat run time, never by editing a shared file in place. - Use
childrengroups to build a clean hierarchy —larkspuroverwebanddb— so variables and targeting apply at the level that matches their scope. - Name groups by role (
web,db,lb) so a play'shosts:line reads like intent, and reserveallfor actions that are genuinely meant to be fleet-wide. - Verify the model with
ansible-inventory --graphbefore running anything, so you see exactly which hosts a group resolves to rather than discovering it mid-run. - Lean on multi-group membership rather than fighting it — let a host sit in its role, environment, and estate groups so each layer of variables attaches where it belongs.
Knowledge Check
What do the two implicit groups all and ungrouped contain?
allholds every host in the inventory;ungroupedholds hosts not placed in a group you definedallcontains every group you defined;ungroupedthen collects every host, grouped or notallcontains only the hosts that answered a ping;ungroupedcontains the ones that failed to connect- Both groups stay empty until you explicitly add hosts to them in the inventory file yourself
Why does it matter that a host can belong to several groups at once?
- Each membership is a place variables attach, so one host layers its role, environment, and estate settings
- Multiple memberships let Ansible run that host's tasks in parallel, scheduling one batch per group it joins
- A host in many groups is reached over a separate, redundant SSH connection for each group it sits in
- It is not allowed — a host must belong to exactly one group or the inventory parse fails
How does inventory let one playbook separate staging from production?
- The play stays generic and you swap the inventory it reads with
-i, so hosts and variables differ but the play does not - The playbook reads a checked-in state file that records which of the two environments is currently the active deploy target
- You add an
environment:flag to every task and branch its steps on whether the value reads staging or prod - Ansible keeps separate state per environment and switches between them automatically on each run
What does a children group relationship model?
- A group that contains other groups, so a parent like
larkspurapplies variables and targeting to its child tiers - A list of hosts that are temporarily disabled and excluded from every run until you re-enable them
- A dependency order that forces one group's tasks to finish completely before the next group's begin
- A set of hosts that inherit their SSH user, key, and port number from a single designated parent host that you name
You got correct