Template Module — Powered By Jinja2
copy’s content: parameter, from the lookups section, wrote literal or lookup-sourced text to a remote destination. template is a different, more powerful idea entirely: an entire separate file, processed through Jinja2, with every expression inside it resolved before the result ever reaches the remote host.
template vs. copy: What’s Actually Different
copy moves a file’s content essentially byte-for-byte — if a file copied with copy happens to contain {{ app_name }}, that literal text shows up unresolved at the destination, exactly as written. template treats its source file as genuine Jinja2 — every {{ }} expression inside it gets evaluated using the current play’s variables and facts, and the rendered result is what actually gets written.
Writing A Template File
app_name = {{ app_name }}
port = {{ app_port }}
debug_mode = {{ enable_debug_logging | default(false) }}Every filter and expression from this entire course works here exactly as it would inside a playbook’s own YAML — default, comparisons, anything.
Using The template Module
- name: Generate app config from template
ansible.builtin.template:
src: app.conf.j2
dest: /etc/myapp/app.confWhere Templates Live: The templates/ Convention
By convention, template source files live in a directory named templates/, alongside the playbook — src: resolves against it automatically, the same relative-path resolution principle already established for lookups earlier in this course. This convention becomes formalized further once roles enter the picture later, but it’s worth adopting now.
A Realistic Example: Generating A Config File From Variables
Combining template with variables and a handler, the way a real deployment actually works:
vars:
app_name: "MyApp"
app_port: 8080
tasks:
- name: Generate app config from template
ansible.builtin.template:
src: app.conf.j2
dest: /etc/myapp/app.conf
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.debug:
msg: "Restarting app due to config change"Templates Are Also Idempotent
Exactly like copy, template only reports changed when the newly rendered content genuinely differs from what’s already at the destination. Run the same task twice with unchanged variables, and the second run reports ok, not changed — the same idempotency principle established since the very first playbook this course ever built, now extended to genuinely generated content.
A Gotcha: The .j2 Extension Is A Convention, Not A Requirement — But Use It Anyway
Ansible doesn’t actually require a template’s source file to end in .j2 — template would process a file named app.conf or app.conf.template exactly the same way. But .j2 is such a strong, near-universal convention that skipping it makes it genuinely harder to tell, at a glance in a directory listing, which files are meant for copy and which are meant for template. Not enforced, worth following anyway.
Best Practices
- Use
template, notcopy, any time a file’s content needs variables or logic resolved before being written —copymoves bytes as-is;templategenuinely generates content. - Always name template source files with a
.j2extension, purely for the clarity it gives anyone browsing the directory later. - Rely on
template’s genuine idempotency exactly likecopy’s — pair it withnotifyfor “only restart if the config genuinely changed” patterns, exactly as established in the handlers section.