100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
DevOps

Loops in Ansible

Use the 'loop' keyword and its variants to run a single task repeatedly over lists, dictionaries, and nested data structures.

Playbooks & TasksIntermediate9 min readJul 10, 2026
Analogies

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.

yaml
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

Was this page helpful?

Topics covered

#DevOps#AnsibleStudyNotes#LoopsInAnsible#Loops#Ansible#Loop#Keyword#StudyNotes#SkillVeris#ExamPrep