Ansible Loops & Iteration

Loops eliminate the need to write the same task multiple times with different values. Instead of five separate tasks to install five packages, you write one task with a loop over a list of package names. This is the 80/20 feature of Ansible efficiency — loops appear in virtually every production playbook.

Basic Loops with loop

- name: Install required packages
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - curl
    - vim
    - git
    - htop

The loop keyword makes Ansible run the task once for each item in the list. The current item is available as {{ item }}. This is equivalent to writing five separate tasks but dramatically more maintainable.

Looping Over a Variable List

vars:
  web_packages:
    - nginx
    - php-fpm
    - php-mysql
  
tasks:
  - name: Install web packages
    apt:
      name: "{{ item }}"
      state: present
    loop: "{{ web_packages }}"

Keeping the list in a variable (or in group_vars) means you can change the packages without modifying the task. This separation of data and logic is a key maintainability principle.

The apt Module's Built-In List Support

For package installation specifically, the apt and yum modules accept a list of package names directly — more efficient than a loop because they install all packages in one operation:

- name: Install multiple packages efficiently
  apt:
    name:
      - nginx
      - curl
      - vim
    state: present

Use this form for packages; use explicit loops for other modules.

Looping Over Dictionaries

- name: Create multiple users
  user:
    name: "{{ item.name }}"
    shell: "{{ item.shell }}"
    groups: "{{ item.groups }}"
    state: present
  loop:
    - name: alice
      shell: /bin/bash
      groups: sudo,developers
    - name: bob
      shell: /bin/zsh
      groups: developers
    - name: deploy
      shell: /bin/bash
      groups: www-data

When each item is a dictionary, access its fields with dot notation: item.name, item.shell. This pattern is extremely common for user management, creating multiple files with different content, and configuring multiple services with different parameters.

Looping with Index: loop_control

- name: Create numbered config files
  copy:
    content: "worker {{ item.index }}: port {{ item.value }}"
    dest: "/etc/app/worker-{{ item.index }}.conf"
  loop: [8001, 8002, 8003, 8004]
  loop_control:
    index_var: item.index    # Access loop index as item.index
    label: "port {{ item }}" # Customise the task output label

Loops with when

- name: Install optional packages
  apt:
    name: "{{ item.name }}"
    state: present
  loop:
    - { name: nginx, required: true }
    - { name: certbot, required: "{{ ssl_enabled }}" }
    - { name: munin, required: "{{ monitoring_enabled }}" }
  when: item.required | bool

The when condition is evaluated for each loop iteration independently. This lets you combine loops with conditionals to install different packages based on runtime conditions.

Registering Loop Results

- name: Check status of all services
  service_facts:
  
- name: Show status for monitored services
  debug:
    msg: "{{ item }} is {{ ansible_facts.services[item + '.service'].state }}"
  loop:
    - nginx
    - postgresql
    - redis
  when: item + '.service' in ansible_facts.services

Try This: User Management Loop

Write a playbook that creates five user accounts on your web servers using a single task with a loop. Each user should have a specific shell and belong to appropriate groups. Add a second task that creates a home directory structure (bin, logs, config subdirectories) for each user using a nested loop structure. Run the playbook and verify with getent passwd that all users were created.

Summary

Loops eliminate repetitive task definitions and replace them with a single parameterised task. The loop keyword iterates over lists, with the current item available as item. Dictionary items use dot notation for field access. The loop_control option provides index access and output label customisation. Loops combine naturally with when conditionals for selective iteration. For package installation, prefer native list arguments over explicit loops for efficiency.