Skip to content

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

templates/app.conf.j2
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.conf

Where 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 .j2template 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, not copy, any time a file’s content needs variables or logic resolved before being writtencopy moves bytes as-is; template genuinely generates content.
  • Always name template source files with a .j2 extension, purely for the clarity it gives anyone browsing the directory later.
  • Rely on template’s genuine idempotency exactly like copy’s — pair it with notify for “only restart if the config genuinely changed” patterns, exactly as established in the handlers section.
Last updated on