Generate real configuration files from variables and facts using the template module and Jinja2 — loops, conditionals, and filters that turn data into config.
Why: copy places a static file; template renders one through Jinja2 first, substituting variables and facts. This is how you generate an nginx.conf, an env file, or any config that differs per host. The template lives in a .j2 file; the module writes the rendered result to the destination.
- name: Render a config from a template
hosts: local
vars:
server_name: example.com
http_port: 8080
tasks:
- name: Generate the site config
ansible.builtin.template:
src: ./nginx.conf.j2
dest: /tmp/nginx.confWhy: a Jinja2 template is plain text with {{ }} for values and {% %} for logic. Anything in scope — vars, facts, registered results — is available. This template reads the variables from the play above.
# nginx.conf.j2
server {
listen {{ http_port }};
server_name {{ server_name }};
# facts work too:
# generated on {{ ansible_facts['hostname'] }}
root /var/www/{{ server_name }};
}Why: templates can iterate and branch, so one template renders a complete file from a list. {% for %} loops over a collection; {% if %} includes a section only when a condition holds. This generates an upstream block with one line per backend server.
# upstream.conf.j2 (servers is a list variable)
upstream backend {
{% for host in servers %}
server {{ host }}:8080;
{% endfor %}
}
{% if enable_tls %}
# TLS is on
listen 443 ssl;
{% endif %}Why: Jinja2 filters (applied with |) reshape data inside the template — provide a fallback, change case, format as JSON, join a list. They keep logic in the template instead of cluttering your variables. default is the one you will reach for most: it supplies a value when one is missing.
worker_processes {{ workers | default(2) }};
app_name = {{ app_name | upper }}
allowed_ips = {{ allow_list | join(", ") }}
config_json = {{ settings | to_json }}