Playbook Strategies
Every playbook in this course, all the way back to the very first one, has quietly relied on a specific execution model without ever naming it. This chapter names it, and covers two genuinely different alternatives.
The Default: linear
Recall from the very first playbook chapter: with more than one host, each task completes on every targeted host before any host moves on to the next task — hosts that finish early wait for slower ones to catch up, and the play proceeds task by task, in lockstep. This is the linear strategy, Ansible’s default, used implicitly in every playbook this course has built so far.
- name: Explicit Linear
hosts: servers
strategy: linear
tasks:
- name: Task one
ansible.builtin.debug:
msg: "..."free: Each Host Races Ahead Independently
strategy: free removes the lockstep entirely — each host proceeds through the whole task list at its own pace, with no waiting on any other host at all:
- name: Free Strategy Demo
hosts: servers
strategy: free
tasks:
- name: Task one
ansible.builtin.debug:
msg: "..."
- name: Task two
ansible.builtin.debug:
msg: "..."A fast host can finish every task while a slow one is still on the first — genuinely useful when hosts have meaningfully different performance characteristics, and there’s no real reason for a fast host to sit idle waiting on a slow one.
A Realistic Consequence Of free
This isn’t free of tradeoffs — the name is a description of behavior, not a recommendation. Terminal output becomes genuinely interleaved and unpredictable under free. More importantly, any assumption that “by the time task N runs, every host has already finished task N-1” is no longer safe. This matters directly for handlers (which normally run at a play’s end — a point that now arrives at different times for different hosts) and for delegate_to patterns, covered in the next chapter, where a task might implicitly assume some other host has already reached a certain point. free is a deliberate tradeoff, not a default to switch to lightly.
The debug Strategy: Automatic Debugging On Every Failure
strategy: debug behaves like linear, but automatically drops into the interactive debugger, from the troubleshooting and debugging section, on every task failure across the whole play — without needing debugger: on_failed set individually anywhere:
- name: Debug Strategy Demo
hosts: servers
strategy: debug
tasks:
- name: Task one
ansible.builtin.debug:
msg: "..."debug Strategy vs. debugger: Keyword — Don’t Confuse The Two
Worth a dedicated, explicit note, since the naming genuinely collides. debugger: is a per-task, per-block, or per-play keyword, controlling exactly when the interactive debugger triggers for whatever it’s attached to, with fine-grained trigger conditions (on_failed, on_unreachable, on_skipped, always). strategy: debug is a whole-play execution model, which happens to also enable debugger-on-failure behavior across everything, as a side effect of using it. They overlap in effect for the on-failure case, but they’re conceptually different mechanisms — one configures a trigger, the other replaces how the entire play executes.
Setting A Strategy
A play-level keyword, as shown throughout this chapter — or set globally via ansible.cfg:
[defaults]
strategy = freePer-play is more common and more deliberate; a global default in ansible.cfg applies silently to every play that doesn’t explicitly override it, which is worth being cautious about.
Best Practices
- Leave
strategyat its default (linear) unless you have a specific reason to change it — most playbooks, especially anything involving handlers ordelegate_to, rely onlinear’s lockstep guarantees whether or not that was ever consciously noticed. - Use
freespecifically when hosts genuinely have very different per-task durations, and there’s no coordination dependency between them. - Prefer
debugger: on_failedon specific tasks overstrategy: debugfor routine troubleshooting — reservestrategy: debugfor deliberately investigating an entire unfamiliar play end to end.