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
- htopThe 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: presentUse 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-dataWhen 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 labelLoops 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 | boolThe 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.servicesTry 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.
