In Ansible, what do the host patterns `webservers:&staging` and `webservers:!decommissioned` select, and how does `--limit` interact with a play's `hosts:` line?
answer
- operators combine group names
- one means both, one means minus
- the flag filters after the play resolves
- it can only ever shrink the set
- preview with a dry listing first
basics
~20 sThe first selects hosts in both webservers and staging (intersection); the second selects hosts in webservers that are not in decommissioned (exclusion). --limit narrows what the play's hosts line already matched — it intersects, and can never add hosts the play did not target.
solid answer
~40 sPatterns compose group and host names with operators: `:` or a comma is union, `:&` is intersection, and `:!` is exclusion, so `webservers:&staging` is hosts in both groups and `webservers:!decommissioned` is the webservers minus anything decommissioned. `--limit` is a separate, later filter: it intersects with whatever the play's `hosts:` already resolved to, so `hosts: webservers --limit db01` runs against nothing and Ansible errors out saying the hosts and limit matched nothing. That one-way property is what makes `--limit` safe as an operational guard rail — it can only ever shrink the target set. In practice I preview with `--list-hosts` before a production run, and remember to quote patterns containing `!` so the shell does not try to expand them.
code
bash · 4 linesansible-playbook -i inventories/prod site.yml \
--limit "webservers:!decommissioned" --list-hosts
ansible -i inventories/prod 'webservers:&staging' --list-hostsgo deeper
Know that a play targets a group name or all, and that patterns can combine names — union with a comma, and ! to exclude a group.
Explain the operators precisely, including intersection with :&, and that --limit is applied on top of the play's own host selection rather than replacing it.
Show the operational discipline: preview with --list-hosts, treat --limit as a guard rail that can only narrow, and keep retired hosts in an excludable group.
Own the convention that keeps blast radius bounded — whether broad plays plus a mandatory limit is safer than narrow plays, and how pipelines supply the limit instead of an operator typing it.
## Patterns select from what the inventory produced A pattern is the expression that turns the inventory into the actual host set for a play. It appears in two places: the `hosts:` line of a play, and the first positional argument of the ad-hoc `ansible` command. The building blocks are host names and group names, combined with operators: - `all` or `*` — every host in the inventory. - `web1:web2` or `web1,web2` — union. The comma form is preferred because it does not collide with anything. - `webservers:&staging` — intersection: only hosts that are in *both*. - `webservers:!decommissioned` — exclusion: everything the pattern has accumulated, minus that group. - `~(web|db).*` — a regular expression against host names. - `web[01:20]` — a numeric range over host names, matching the range syntax used when declaring hosts. Intersection is what makes a two-axis inventory work. If hosts are grouped both by role and by environment, then `webservers:&staging` is the only way to say "web servers, but only the staging ones" without maintaining a third group called `staging_webservers` that someone will forget to update. Two mechanical traps. In YAML, a value beginning with certain characters must be quoted, and in a POSIX shell an unquoted `!` can trigger history expansion — so write `hosts: "webservers:!decommissioned"` and quote the argument on the command line. And a pattern that matches nothing is not silently ignored: Ansible tells you the pattern did not match, which is the correct behaviour but surprises people who expected a no-op. ## `--limit` is a second, narrowing filter `--limit` (also `-l`) accepts the same pattern syntax but is applied *after* the play's own `hosts:` line, as an intersection with it. This has one consequence worth stating precisely in an interview: **`--limit` can only shrink the target set, never grow it.** A play declaring `hosts: webservers` and invoked with `--limit db01` runs against nothing, and Ansible stops with an error saying the specified hosts and/or limit did not match any hosts. It does not quietly reach outside the play. That one-way property is exactly why `--limit` is the standard operational guard rail. A team can keep broad plays — `hosts: all` inside a role-driven site playbook — and require every human-initiated run to pass a limit, confident that the flag cannot widen the blast radius by accident. The failure mode is the opposite one: forgetting `--limit` and getting the full play, so pipelines that rely on it should supply it in the job definition rather than trusting an operator to type it. ## Previewing The cheap habit before any production run: ```bash ansible-playbook -i inventories/prod site.yml --limit "webservers:!decommissioned" --list-hosts ansible webservers:'&'staging -i inventories/prod --list-hosts ``` `--list-hosts` resolves the pattern and prints the hosts without connecting to anything. It answers "is this pattern what I think it is" in a second and costs nothing. `--check` is the next step down that road, but `--list-hosts` is the one that catches pattern mistakes, because a wrong pattern in check mode still tells you about the wrong hosts. ## Rolling execution is a different lever Candidates sometimes reach for `--limit` when they mean batching. Narrowing to a subset and running repeatedly is a manual, error-prone way to stage a rollout; the play-level `serial` keyword expresses "this many hosts at a time" declaratively over the full target set. `--limit` answers *which* hosts; `serial` answers *how many at once*. Knowing that they are different questions is part of the answer here. ## What interviewers are really probing This question is a proxy for how you operate. A candidate who reaches for `--list-hosts`, quotes their patterns, knows that `--limit` intersects rather than replaces, and keeps decommissioned hosts in an excludable group is describing a team that does not accidentally reconfigure production. A candidate who thinks `--limit` overrides `hosts:` has just described how a staging run reaches a production database.
- A play declares `hosts: webservers` and you pass `--limit db01`. What happens?Nothing runs. `--limit` intersects with the hosts the play already selected, and `db01` is not among them, so the result is empty and Ansible errors out saying the hosts and/or limit did not match. The flag can never pull in a host the play did not target.
- Why keep a `decommissioned` group instead of deleting the hosts from the inventory?Because an excludable group keeps the record of what existed while removing it from every pattern that uses `:!decommissioned`, and it is reviewable in a diff. Deleting entries loses the history and makes it easy for someone to re-add a host that was retired for a reason.
- When would you use the play-level `serial` keyword rather than `--limit`?When the question is *how many at a time*, not *which hosts*. `serial` runs the full target set in batches so a rolling restart never takes the whole fleet down at once, and it is declared in the play. Using repeated `--limit` invocations to fake batching is manual and easy to get wrong.
saying these in an interview costs you the question
- Thinking --limit replaces the play's hosts line
- Believing --limit can add hosts outside the play
- Assuming a pattern matching nothing is a silent no-op
- Using repeated --limit runs instead of serial for batching
- Forgetting to quote patterns containing ! in the shell