// learn Β· linux Β· rhce Β· 10 min

Ansible templates.

One Jinja2 `.j2` file, a tailored config on every host. The template module renders the file with each machine's own variables and facts before it lands β€” so a single `nginx.conf.j2` becomes a correct, host-specific config everywhere.

Ansible templates animated tutorial. The ansible.builtin.template module renders a Jinja2 .j2 file with each host's variables and facts and copies the result, unlike copy which ships bytes verbatim. Covers expression tags with double braces, control structures for and if with statement tags, whitespace trimming, filters including default, upper, lower, join, and password_hash, the ansible_managed header, and reading hostvars and groups so one template produces a per-host config. RHCE EX294 ready.
// ansible Β· templates (jinja2)

One `.j2` file, a tailored config on every host. Watch the same template render into different concrete files for servera and serverb as we spotlight the template module, expressions, control structures, filters, and per-host facts.

// before you start
you should know
  • Β·Ansible playbook basics (tasks, modules, vars)
  • Β·Comfortable with the copy module
  • Β·Know what facts and group/host vars are
by the end you'll

Render Jinja2 templates per host with the template module: insert values with {{ }}, branch and loop with {% %}, transform with filters, and use facts so one .j2 produces a correct file on every machine.

pace: 10 minutes

ansible β€” step 1 / 5 Β· template
🧬templates/nginx.conf.j2jinja2 source
1# {{ ansible_managed }}
2server_name {{ inventory_hostname }};
3listen {{ http_port | default(80) }};
4worker_processes {{ ansible_facts['processor_vcpus'] }};
template module→renders per host
πŸ–₯️serverarendered
1# Ansible managed
2server_name servera;
3listen 80;
4worker_processes 2;
πŸ–₯️serverbrendered
1# Ansible managed
2server_name serverb;
3listen 8080;
4worker_processes 8;

The template module

`ansible.builtin.template` renders a Jinja2 `.j2` file on the control node and copies the RESULT to dest β€” unlike `copy`, which ships the bytes verbatim. You still set owner, group, and mode just like copy, and you can add `validate:` to run a syntax check on the rendered file before it replaces the live one. By convention the source `.j2` files live in a `templates/` directory beside your playbook, so Ansible finds them with a bare filename.

$ansible.builtin.template:\n src: httpd.conf.j2\n dest: /etc/httpd/conf/httpd.conf\n mode: '0644'
$validate: "httpd -t -f %s"
$templates/httpd.conf.j2
// key insight

`copy` ships bytes; `template` ships a Jinja2 file that is RENDERED with each host's variables and facts first. That one difference means a single `nginx.conf.j2` becomes a correct, host-specific config on every machine β€” no per-host files to maintain.

// exam-ready Β· template module & jinja2
$ansible.builtin.template src=X.j2 dest=Y
$mode / owner / group
$validate: "cmd %s"
$templates/ dir
${{ var }}
${% if %}…{% endif %}
${% for x in list %}…{% endfor %}
${%- … -%}
$| default(X)
$| upper / | lower
$| join(',')
$| password_hash('sha512')
${{ ansible_managed }}
$hostvars[h] / groups['grp']
// check yourself
4 quick questions
Q1

When should you use the `template` module instead of `copy`?

Q2

What's the difference between `{{ }}` and `{% %}` in a Jinja2 template?

Q3

A variable might be undefined and you want a sensible fallback in the rendered file. Which filter?

Q4

How can one template file produce a different nginx config on every web server?

These aren't graded β€” they're just for active recall, which is what actually makes the lesson stick.

πŸ””
// next: react to change

Ansible handlers & notifications

A template only matters if the service picks up the change. Use `notify` and handlers to restart a service only when its config actually changed β€” never on an unchanged run.

open β†’
// more in systems

keep going β€” these pair well with what you just learned.

see all systems β†’
back to RHCSA / RHCE trackall lessons