The loop Keyword
The modern way to repeat a task over a list of items in Ansible is the 'loop' keyword, which replaces the older with_items and other with_* lookups for the vast majority of use cases. Inside the task, each item is available as the variable 'item' by default, so a single task definition — like installing a package — can install an entire list of packages, one iteration per list entry, without duplicating the task.
Cricket analogy: The 'loop' keyword is like a bowler running through their entire pre-over checklist (mark run-up, check field, polish ball) once for every single delivery in the over, using the same fixed routine each time.
Looping Over Lists and Dictionaries
For a simple list of strings or numbers, 'loop' can reference a variable directly, and 'item' holds each value in turn. For a list of dictionaries — say, users each with a name and a set of groups — 'item' becomes the whole dictionary, so you access fields with item.name or item['groups']. Looping over an actual dictionary (not a list of dictionaries) requires converting it first with the 'dict2items' filter, which turns each key/value pair into an object with 'key' and 'value' attributes.
Cricket analogy: Looping over a list of dictionaries is like reading a squad sheet where each row has a player's name AND their batting position — item.name pulls the player, item.position pulls their slot, both from the same row.
tasks:
- name: Install multiple packages
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- git
- curl
- htop
- name: Create users with specific groups
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: /bin/bash
loop:
- { name: 'alice', groups: 'sudo,docker' }
- { name: 'bob', groups: 'developers' }
loop_control:
label: "{{ item.name }}"
- name: Wait for API health check to pass
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200
register: health_result
until: health_result.status == 200
retries: 5
delay: 10
loop_control, subelements, and until
'loop_control' customizes loop behavior: 'loop_var' renames the default 'item' variable (essential when nesting one loop inside another, since inner and outer loops can't both use 'item'), and 'label' controls what gets printed in output for each iteration, useful for hiding sensitive dictionary contents like passwords. For nested data — like a list of users, each with their own list of authorized SSH keys — the 'subelements' filter flattens the structure so a single task loop can process every (user, key) pair. Separately, 'until' combined with 'retries' and 'delay' turns a task into a polling loop that repeats the same task against itself until a condition is met or retries are exhausted, distinct from looping over a static list.
Cricket analogy: loop_control's loop_var is like renaming which specific counting variable tracks overs versus balls when nesting an over-loop inside an innings-loop, so the two counters don't collide.
The old 'with_items', 'with_dict', and similar with_* lookups still work but are considered legacy; prefer 'loop' plus filters like dict2items and subelements for new playbooks. Also remember that 'until' retries the SAME task repeatedly against its own registered result — it is not a substitute for 'loop', which iterates a task once per distinct list item.
- 'loop' is the modern replacement for with_items and most other with_* lookups.
- Inside a loop, each element is available as 'item' by default.
- Looping over dictionaries requires the dict2items filter to get key/value pairs.
- loop_control.loop_var renames 'item', essential for nested loops.
- loop_control.label controls what's printed per iteration, useful for hiding sensitive data.
- subelements flattens nested list-of-lists structures into flat pairs for a single loop.
- 'until' + 'retries' + 'delay' polls the same task repeatedly until a condition is met — different from looping over a list.
Practice what you learned
1. What variable name holds each element by default inside a 'loop'?
2. How do you loop over an actual dictionary (not a list of dictionaries) in Ansible?
3. Why would you use loop_control.loop_var?
4. What does the 'subelements' filter do?
5. How does 'until' combined with 'retries' and 'delay' differ from 'loop'?
Was this page helpful?
You May Also Like
Conditionals in Ansible
Use the 'when' clause and Jinja2 expressions to run tasks conditionally based on facts, variables, and previous task results.
Tasks and Modules
Understand how Ansible tasks invoke modules to perform idempotent actions, and how to choose the right module for the job.
Playbook Structure Basics
Learn how Ansible playbooks are organized as YAML documents of plays and tasks, and how top-level keys like hosts, vars, and roles fit together.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics