In a previous article we talked about Ansible, a very useful provisioning free and open source software written in Python, which we can use to automate tasks on multiple machines. We saw how to install it on some of the most used Linux distributions and the basic concepts behind its usage. In this article we focus on how to use loops inside Ansible playbooks in order to perform a single task multiple times with different data.
In this tutorial you will learn:
- How to use loops inside Ansible playbooks
- How to loop over a list of items
- How to loop over a list of hashes
- How to specify time interval between loop iterations
- How to keep track of the loop index

Software requirements and conventions used
Category | Requirements, Conventions or Software Version Used |
---|---|
System | Distribution independent |
Software | Ansible |
Other | None |
Conventions | # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command$ – requires given linux-commands to be executed as a regular non-privileged user |
Introducing loops
Let’s start with a simple, single task. Suppose we want to be sure a file has a specific set of permissions applied to it. To translate the concept into an Ansible task, we would use the ansible.builtin.file
module and write:
- name: Apply permissions
ansible.builtin.file:
path: /foo.conf
mode: '600'
With the task definition above we declared a state: the /foo.conf
file must have the 600
permission mode applied to it (its owner should be able to read it and write to it; no privileges should be assigned to its group and the rest of the world). Suppose we want to do the same thing for multiple files; how should we proceed?
Of course writing the exact same task for each file would be a very bad idea, since we would repeat ourselves. The ideal thing would be to use the same task, but with different data. This is a typical case when the right thing to do is to use a loop. Here is what we could write:
- name: Set permissions
ansible.builtin.file:
path: "{{ item }}"
mode: '600'
loop:
- /foo.conf
- /bar.conf
- /baz.conf
When the task is executed, the following output is returned in the console:
TASK [Apply permissions] ******************************************************* changed: [localhost] => (item=/foo.conf) changed: [localhost] => (item=/bar.conf) changed: [localhost] => (item=/baz.conf)
What we did above is a very simple example of a loop in an Ansible playbook. As you can see, we used the loop
keyword at the same indentation level of the task name. In this case we provided, using the yaml syntax, a list of paths; then, in the task itself, we used the item
variable to reference each one of them. At each iteration this variable will reference one element of the list we specified.
Pretty easy! In this trivial example we assigned the same permissions to all the files in the list; what if we want to assign a different permission mode to each one of them?
Specifying multiple parameters by iterating over a list of hashes
As we said, in the previous example we simple iterated over a list; there may be cases, however, when we need to specify multiple parameters at each iteration. In those cases we want to define and iterate over a list of hashes instead.
Suppose we want to set the permissions of multiple files with the same task, just like we did before, but we want to assign each file a different permission mode. How could we do it? In a case like that, iterating over a simple list would not be enough. What we want to do is to iterate over a list of hashes. Inside each hash we specify the parameters which should be used and their values. Here is an example:
- name: Set permissions
ansible.builtin.file:
path: "{{ item.path }}"
mode: "{{ item.mode }}"
loop:
- { path: '/foo.conf', mode: '600' }
- { path: '/bar.conf', mode: '640' }
- { path: '/baz.conf', mode: '640' }
Let’s take a look at what we did above. Just like in the previous example we used the loop
instruction to create a loop, this time, though, we specified a list of hashes. Inside each hash we used the path
and mode
keys, and assigned them the appropriate values for each file.
Notice that the key names here are completely arbitrary: they should not necessarily correspond to the parameters used in the task. Inside the task itself, just as before, the value assigned at each iteration of the loop is referenced via the item
variable. In this case each item
would be one of the hashes we specified; to access the keys in each hash, we use a .
, just like we would do to access a property of a Python object, so each time, for example, item.path
will reference the value assigned to that key in the hash.
Controlling time between iterations
There could be some cases in which we want to set the amount of time which should pass between the iterations of a loop. How can we do this in a playbook? All we have to do is to use the pause
directive inside the loop_control
section. Here is a trivial ansible loop example in which each iteration runs 5
seconds after the previous one:
- name: Print message
ansible.builtin.debug:
msg: "{{ item }}"
loop:
- Hello
- World
loop_control:
pause: 5
Keeping track of the iteration index
Just like we did in the previous example we can use the loop_control
section to keep track of the loop iteration count. All we have to do is to use the index_var
directive. The variable we specify as value to this directive will contain the index of the current iteration (zero-based). Here is an example:
- name: Print message
ansible.builtin.debug:
msg: "The item is {{ item }} and loop index is {{ i }}"
loop:
- hello
- world
loop_control:
index_var: i
The task we defined in the above example is very trivial and has not a real use; however, it could be useful to display how the iteration index is increased. If we execute it, we obtain the following output:
TASK [Print message] *********************************************************** ok: [localhost] => (item=Hello) => { "msg": "The item is Hello and loop index is 0" } ok: [localhost] => (item=World) => { "msg": "The item is World and loop index is 1" }
Conclusions
In this article we learned the basic usage of loops inside Ansible playbooks and readers were provided some introductory Ansible loops examples. We saw how to iterate over a simple list of items and over a list of hashes, each one containing a set of key-value pairs.
We also saw how to specify how much seconds should pass between each iteration of a loop, and how to keep track of the iteration index in a variable using the loop_control
section and, respectively, the pause
and index_var
directives. Here we barely scratched the surface of what is possible to achieve with loops. For a more in depth knowledge please consult the official Ansible documentation!