Ansible Guide
Flat isometric illustration of a pink cube with dark slot vents on its face floating above a network of white-topped pink nodes on a dark platform.
Reference

ansible-playbook Options: Flags That Change a Run

A working reference to ansible-playbook flags for scope, preview, task selection and diagnostics, with the check-mode and precedence traps that bite.

By Ansible Guide Editorial · · 6 min read

A playbook file is only half of a change. The other half is the invocation, and the flags on ansible-playbook decide how many machines are in scope, whether anything is written, which tasks run at all, and how much you can see when something goes wrong. This is a reference to the options that change the outcome of a run, grouped by the question they answer.

One piece of vocabulary first, because it causes confusion: a runbook is the human procedure for handling a situation, usually prose with decision points. A playbook is an executable YAML file that automates some of those steps. A playbook can implement a runbook, but the terms are not interchangeable, and only one of them has command-line flags.

Scope: which hosts

-i, --inventory points at an inventory source. It can be given more than once, and each source is merged, which is how a static file of exceptions gets layered on top of a dynamic cloud source. It also accepts a directory. Without it, Ansible falls back to the configured default host list.

-l, --limit narrows the run to a subset of the hosts the play already targets, using the same pattern syntax as inventory. It only ever subtracts: a host that is not in the play cannot be added back by --limit. This is the single most useful safety flag on the command, and the habit worth building is to run new changes against one host with --limit before widening.

--list-hosts resolves the pattern and prints the hosts that would be affected without running anything. Use it whenever a pattern combines groups, because intersection and exclusion patterns are easy to get subtly wrong. Pattern syntax is covered in inventory groups, variables and patterns.

Preview: what would happen

--syntax-check parses the playbook and everything it imports and exits. It catches YAML and structural errors in a second and belongs in every pipeline as a first gate.

-C, --check runs in check mode, which the documentation describes as a simulation: modules that support check mode report the changes they would have made, and modules that do not support it report nothing and do nothing. That second half is the trap. A play built on command or shell tasks tells you almost nothing in check mode, and a clean check run is not evidence that the play is safe.

There is a second documented limitation: check mode will not generate output for tasks whose conditionals depend on registered variables from earlier tasks. If task 3 was skipped because it makes changes, task 4’s when: result.changed has nothing real to test. Long chains of registered state degrade into noise under --check.

Two per-task controls exist for this. check_mode: false forces a task to run normally even when the playbook is called with --check, which is the right setting for a read-only gathering task whose output later tasks depend on. check_mode: true forces a task to simulate always. The magic variable ansible_check_mode is true during a check run and can be used in when: to skip a task that cannot work in simulation, or in ignore_errors to tolerate one that will fail.

-D, --diff prints before-and-after comparisons from modules that support diff mode. Combined with --check it reports the changes that would have been made, which is the standard preview pair for template and file work. Be aware that diff output prints file contents, so a task rendering credentials needs diff: false on the task, which is the documented control for exactly this, or no_log: true to suppress the whole result. Without one of them the secret ends up in the run log.

--list-tasks and --list-tags print the tasks and tags a play would execute, including everything pulled in from roles. On an inherited playbook these two flags are the fastest way to find out what it actually does.

Selection: which tasks

-t, --tags runs only tagged tasks; --skip-tags runs everything except them. Tags are the intended mechanism for splitting a large play into a fast path and a full path.

--start-at-task begins execution at the first task whose name matches. It is useful when a long play failed near the end, and it carries a trap that costs real time: skipped tasks do not run, so the facts they set and the variables they registered do not exist. A play that starts at task 30 and reads a variable registered by task 12 fails with an undefined variable, or worse, uses a stale cached value. Restarting mid-play is only safe when the tasks before the start point set no state the remainder depends on.

--step prompts before each task. It is an interactive tool for walking an unfamiliar play, not something to leave in a script.

Variables, and the precedence rule that surprises people

-e, --extra-vars sets variables at the command line, as key=value, as JSON or YAML, or as @vars.yml to read a file. Extra vars sit at the top of Ansible’s variable precedence, so they override values set anywhere else. That makes them the right tool for a one-off override and the wrong tool for anything permanent, since they are invisible to anyone reading the repository.

The subtler rule concerns settings that are not variables. Ansible’s precedence appendix orders its categories from lowest to highest as configuration settings, then command-line options, then playbook keywords, then variables. Each category overrides every lower one. The consequence is that a play declaring remote_user: deploy beats -u root on the command line, and a play with strategy: linear beats a global strategy = free in ansible.cfg. When a flag appears to be ignored, check the play keywords before blaming the flag. Strategy keywords in particular are covered in linear vs free vs host_pinned.

Concurrency and connection

-f, --forks sets how many hosts are worked on simultaneously. The documented default is 5, which is conservative for anything above a handful of machines and is the first setting to raise on a large inventory. Raise it in steps while watching control node memory and CPU, because each fork is a process holding an outbound connection. The inventory sizing calculator models the relationship between host count, forks and batch runtime.

-u, --user and --private-key set the SSH identity, and -c, --connection selects the connection plugin, with local being the common override for playbooks acting on the control node itself.

-b, --become, --become-user and -K, --ask-become-pass handle privilege escalation. -k, --ask-pass prompts for the SSH password, which is only relevant where key authentication is not in place.

--vault-id, --ask-vault-password and --vault-password-file supply Vault credentials at run time. Multiple --vault-id values can be passed when a repository holds secrets encrypted under more than one identity.

Diagnostics

-v through -vvvv raise verbosity. Level three shows module arguments and task internals; level four adds connection debugging, which is where SSH negotiation, pipelining and privilege escalation problems become visible. Verbose output includes module arguments, so treat it as sensitive.

--flush-cache clears the fact cache for the inventory before running, which is the fix when a cached fact no longer matches a rebuilt machine.

One historical note worth checking before relying on it: --limit @failed.retry depends on retry files, and the retry_files_enabled configuration setting defaults to False. On a current installation no .retry file is written unless it has been explicitly enabled in ansible.cfg.

A sequence that avoids most incidents

  1. --syntax-check to catch structural errors.
  2. --list-hosts to confirm the pattern resolves to what you meant.
  3. --list-tasks on an unfamiliar play to see what it will do.
  4. --check --diff --limit one-host to preview, remembering that command and shell tasks report nothing.
  5. --limit one-host for a real run against a single machine.
  6. Widen the limit, or add serial, once the single host is verified.

Common mistakes

Treating a clean --check run as proof of safety on a play full of shell tasks. Using --start-at-task on a play whose later tasks depend on earlier registered variables. Reaching for -e to set something that belongs in group_vars, then losing track of why production differs. Debugging an ignored -u or -f flag that a play keyword is overriding. Running --diff on a task that renders secrets without no_log. Leaving -vvvv output in a shared log.

For the execution model these flags are steering, start with how Ansible runs a playbook.

Sources

  1. Ansible documentation: ansible-playbook command line tool
  2. Ansible documentation: Validating tasks: check mode and diff mode
  3. Ansible documentation: Controlling how Ansible behaves: precedence rules
  4. Ansible documentation: Ansible Configuration Settings

Related