Dynamic Configuration Files
Most applications require configuration files — nginx virtual host configs, Java properties files, systemd unit files. These files are almost identical across environments, but differ in a few values: database hostnames, port numbers, log levels, API endpoints.
Ansible uses Jinja2, a Python templating engine, to generate these files dynamically. You write a template file (.j2 extension by convention) with placeholders, and Ansible renders it with the correct variable values for each target host.
A template file (.j2 extension by convention) looks like the target config file with {{ variable }} placeholders dropped in wherever a value needs to differ per host. Deploying it is a single template task that renders it and copies the result to its destination. When this task runs, Ansible substitutes each placeholder with that host's variable value, renders the complete file, and copies it to the destination. If the rendered content differs from what's already on disk, the task reports changed. If it's identical, it reports ok.
Jinja2 Filters
Filters transform variable values using the pipe | character, applied at render time: providing a fallback for an undefined variable, forcing case, replacing characters, joining a list into a string, counting a list's length, or picking between two values based on a condition. All of this happens inline, inside the {{ }} expression itself, without any separate logic in the playbook.
Jinja2 Loops in Templates
When you need to generate repetitive configuration blocks — multiple nginx location blocks, multiple firewall rules, multiple database connection entries — a Jinja2 for loop inside the template iterates over a list variable and renders one block per item, each with its own host, port, and path substituted in. An item that overrides a default (a custom timeout, say) gets that value instead of the fallback; every other item still gets the default — the same conditional-default pattern as the filters above, just repeated across a loop.