Ansible Template Loop: Iterate Lists & Dicts in Jinja2 Templates
By Luca Berton · Published 2024-01-01 · Category: installation
How to loop over lists and dictionaries in Ansible Jinja2 templates. Use for loops in template files, iterate variables, generate dynamic configs.

How to use a loop in a file template to the target host with Ansible?
This is extremely useful for service configuration files, placeholder web pages, reports, and so much more use cases.See also: Ansible Start & Enable Service on Boot: systemd Module Guide
Ansible loop in file template
- ansible.builtin.template
- Template a file out to a target host
- ansible_managed, template_host, template_uid, template_path, template_fullpath, template_destpath, and template_run_date
ansible-core and is included in all Ansible installations.
It templates a file out to a target host. Templates are processed by the Jinja2 templating language.
Also you could use also some special variables in your templates: ansible_managed, template_host, template_uid, template_path, template_fullpath, template_destpath, and template_run_date.
It supports a large variety of Operating Systems.
For basic text formatting, use the Ansible ansible.builtin.copy module or for empty file Ansible ansible.builtin.file module.
For Windows, use the ansible.windows.win_template module instead.
Parameters
- src path - template ("templates/" dir)
- dest path - target location
- validate string - validation command before ("%s")
- backup boolean - no/yes
- mode/owner/group - permission
- setype/seuser/selevel - SELinux
## Playbook
Loop in file template with Ansible Playbook. You could find more information about Magic Variables: https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
code
- generate_myhosts.yml
---
- name: template module Playbook
hosts: all
become: true
tasks:
- name: generate /etc/myhosts file
ansible.builtin.template:
src: templates/hosts.j2
dest: /etc/myhosts
owner: root
group: root
mode: '0644'
- hosts.j2
# {{ ansible_managed }}
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in group['all'] %}
{{ hostvars[host]['ansible_host'] }} {{ hostvars[host]['inventory_hostname'] }} {{ hostvars[host]['inventory_hostname_short'] }}
{% endfor %}
execution
$ ansible-playbook -i apply\ template/inventory apply\ template/generate_myhosts.yml
PLAY [template module Playbook] ***********************************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [demo.example.com]
TASK [generate /etc/myhosts file] *****************************************************************
changed: [demo.example.com]
PLAY RECAP ****************************************************************************************
demo.example.com : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
before execution
$ ssh devops@demo.example.com
Last login: Tue Nov 23 17:10:30 2021 from 192.168.0.101
[devops@demo ~]$ sudo su
[root@demo devops]# ls -al /etc/myhosts
ls: cannot access '/etc/myhosts': No such file or directory
[root@demo devops]#
after execution
$ ssh devops@demo.example.com
Last login: Tue Nov 23 17:32:39 2021 from 192.168.0.101
[devops@demo ~]$ sudo su
[root@demo devops]# ls -al /etc/myhosts
-rw-r--r--. 1 root root 214 Nov 23 17:32 /etc/myhosts
[root@demo devops]# cat /etc/myhosts
# Ansible managed
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.0.190 demo.example.com demo
[devops@demo ~]$ exit
logout
Connection to demo.example.com closed.
ansible-pilot $ cat apply\ template/inventory
demo.example.com ansible_host=192.168.0.190
[all:vars]
ansible_connection=ssh
ansible_user=devops
See also: Ansible Stop & Disable Service: systemd Module Guide (with Examples)
Conclusion
Now you know how to use a for loop in a Jinja2 template file with Magic Variables with Ansible.Basic Loop in Template
{# templates/hosts.j2 #}
{% for host in groups['webservers'] %}
{{ hostvars[host].ansible_host }} {{ host }}
{% endfor %}- template:
src: hosts.j2
dest: /etc/hosts.d/webservers
become: trueSee also: ansible.builtin.template: Deploy Jinja2 Templates with Ansible (Guide)
Loop with Index
{% for server in servers %}
server.{{ loop.index }} = {{ server.host }}:{{ server.port }}
{% endfor %}| Variable | Description |
|---|---|
loop.index | 1-based iteration |
loop.index0 | 0-based iteration |
loop.first | True on first iteration |
loop.last | True on last iteration |
loop.length | Total items |
Nginx Upstream
upstream backend {
{% for host in groups['appservers'] %}
server {{ hostvars[host].ansible_host }}:{{ app_port }};
{% endfor %}
}With Conditionals
{% for user in users %}
{% if user.active %}
{{ user.name }}:x:{{ user.uid }}:{{ user.gid }}:{{ user.comment }}:{{ user.home }}:{{ user.shell }}
{% endif %}
{% endfor %}Generate Config Sections
{% for site in websites %}
server {
listen {{ site.port | default(80) }};
server_name {{ site.domain }};
root {{ site.root }};
{% if site.ssl | default(false) %}
listen 443 ssl;
ssl_certificate /etc/ssl/{{ site.domain }}.crt;
ssl_certificate_key /etc/ssl/{{ site.domain }}.key;
{% endif %}
}
{% endfor %}Loop Over Dictionary
{% for key, value in app_config.items() %}
{{ key }}={{ value }}
{% endfor %}
{# With sorting #}
{% for key in app_config.keys() | sort %}
{{ key }}={{ app_config[key] }}
{% endfor %}Nested Loops
{% for group in server_groups %}
[{{ group.name }}]
{% for server in group.servers %}
{{ server.hostname }} ansible_host={{ server.ip }}
{% endfor %}
{% endfor %}Join Filter (Alternative to Loop)
{# Instead of a loop for simple lists #}
allowed_hosts = {{ allowed_ips | join(', ') }}
{# Result: allowed_hosts = 10.0.1.1, 10.0.2.1, 10.0.3.1 #}Whitespace Control
{# Trim whitespace with - #}
{% for item in list -%}
{{ item }}
{%- endfor %}
{# No blank lines between items #}Practical: SSH Config
{% for host in groups['all'] %}
Host {{ host }}
HostName {{ hostvars[host].ansible_host }}
User {{ hostvars[host].ansible_user | default('deploy') }}
Port {{ hostvars[host].ansible_port | default(22) }}
{% if hostvars[host].ansible_ssh_private_key_file is defined %}
IdentityFile {{ hostvars[host].ansible_ssh_private_key_file }}
{% endif %}
{% endfor %}FAQ
How do I avoid trailing commas in loops?
servers = [
{% for s in servers %}
"{{ s }}"{{ "," if not loop.last else "" }}
{% endfor %}
]Can I break out of a loop?
Jinja2 doesn't support break. Use selectattr or rejectattr to filter the list before looping.
How do I handle empty lists?
{% if servers %}
{% for s in servers %}
...
{% endfor %}
{% else %}
# No servers configured
{% endif %}Related Articles
Category: installation
Watch the video: Ansible Template Loop: Iterate Lists & Dicts in Jinja2 Templates — Video Tutorial