Ansible OEL 8 DevOps · OEL 8 · Fundamentals

AnsibleTemplates with Jinja2

How the template module renders configuration files — the syntax, the most useful filters, conditionals and loops, and the four whitespace-control tricks that separate clean output from messy.

A Jinja2 template is a text file with placeholders. When Ansible's template module runs, it substitutes every placeholder with the resolved variable value, then copies the result to the target host. It's how you generate config files that adapt to each host without writing one config file per host.

Template (.j2) [mysqld] server-id = {{ srv_id }} port = {{ port }} {% if repl %} log-bin = on {% endif %} Variables srv_id: 1 port: 3306, repl: true Jinja2 render engine substitute, evaluate my.cnf (rendered) [mysqld] server-id = 1 port = 3306 log-bin = on Result is pushed to the remote host as the final config file
MarkupPurposeExample
{{ ... }}Print a valueport = {{ mysql_port }}
{% ... %}Statement (if, for, set){% if enable_ssl %}...{% endif %}
{# ... #}Comment (stripped from output){# this never appears #}
JINJA2 — templates/my.cnf.j2
# Managed by Ansible — do not edit by hand
[mysqld]
server-id           = {{{{ server_id }}}}
port                = {{{{ mysql_port | default(3306) }}}}
bind-address        = {{{{ ansible_default_ipv4.address }}}}
datadir             = {{{{ mysql_datadir }}}}
socket              = {{{{ mysql_datadir }}}}/mysql.sock
log-error           = /var/log/mysqld.log
pid-file            = {{{{ mysql_datadir }}}}/mysqld.pid

# Tuning — sized to host RAM
innodb_buffer_pool_size = {{{{ (ansible_memtotal_mb * 0.7) | int }}}}M
max_connections          = {{{{ mysql_max_connections | default(200) }}}}

{{% if mysql_role == "primary" %}}
# Primary-only settings
log-bin             = mysql-bin
binlog_format       = ROW
gtid_mode           = ON
enforce_gtid_consistency = ON
{{% endif %}}

{{% if mysql_replicas is defined and mysql_replicas | length > 0 %}}
# Replicas registered in inventory:
{{% for replica in mysql_replicas %}}
# - {{{{ replica }}}}
{{% endfor %}}
{{% endif %}}
YAML — calling the template
- name: Render /etc/my.cnf from template
  ansible.builtin.template:
    src: my.cnf.j2
    dest: /etc/my.cnf
    owner: root
    group: root
    mode: "0644"
    backup: true
  notify: restart mysql
FilterWhat it doesExample
default(x)Use x if value is undefined{{ port | default(3306) }}
upper / lowerCase conversion{{ name | upper }}
lengthList or string length{{ users | length }}
join(sep)List → string{{ ips | join(',') }}
split(sep)String → list{{ csv | split(',') }}
int / floatType conversion{{ '42' | int }}
map('attr', 'k')Pluck k from each item{{ users | map(attribute='name') | list }}
selectattrFilter list of dicts{{ users | selectattr('admin') | list }}
combineMerge two dicts{{ defaults | combine(overrides) }}
to_jsonRender as JSON{{ data | to_json }}
regex_replaceSubstitute by regex{{ s | regex_replace('-', '_') }}
b64encodeBase64 encode{{ pwd | b64encode }}
JINJA2 — if / elif / else
{{% if mysql_role == "primary" %}}
# Primary settings
log-bin = mysql-bin
{{% elif mysql_role == "replica" %}}
# Replica settings
read_only = ON
{{% else %}}
# Default — standalone
{{% endif %}}
JINJA2 — for loops with loop variables
# Inventory of all DB hosts
{{% for host in groups['databases'] %}}
{{{{ hostvars[host].ansible_host }}}}  {{{{ host }}}}
{{% endfor %}}

# With loop.index for numbered output
{{% for user in admin_users %}}
user{{{{ loop.index }}}} = {{{{ user }}}}
{{% endfor %}}

# With if-else inside the loop
{{% for port in firewall_ports %}}
allow {{{{ port }}}}{{% if not loop.last %},{{% endif %}}
{{% endfor %}}

By default, Jinja2 leaves the newlines around {% %} tags in the output, which produces messy files. Use - on the tag to strip whitespace:

JINJA2 — whitespace stripping
{{# without trim — leaves blank lines #}}
{{% for u in users %}}
{{{{ u }}}}
{{% endfor %}}

{{# with trim — clean output #}}
{{% for u in users -%}}
{{{{ u }}}}
{{% endfor %}}

{{# strip both ends #}}
{{%- if condition -%}}
content
{{%- endif -%}}
💡 Tip: In ansible.cfg set jinja2_extensions = jinja2.ext.do to enable the do tag for in-place list/dict mutation. Set trim_blocks = True in your template if you'd rather have whitespace stripping be the default.