Ansible Templates: Jinja2 Template Module Complete Guide
By Luca Berton · Published 2024-01-01 · Category: troubleshooting
Complete guide to Ansible templates. Use Jinja2 templating with variables, loops, conditionals, and filters to generate dynamic configuration files.
Ansible templates are a powerful feature that enables dynamic generation of configuration files and scripts during automation workflows. Using Jinja2 templating, Ansible templates allow for flexibility and adaptability in creating infrastructure and application configurations. This article explains what templates are, how they work, and their benefits.
What Are Ansible Templates?
Ansible templates are Jinja2-based files used to create dynamic configuration files or scripts. These templates can include variables, conditionals, loops, and filters, allowing for highly customized output tailored to the specific needs of managed systems.
Key Features:
- Dynamic Content: Generate files with runtime-specific data.
- Customization: Use variables and logic to create tailored configurations.
- Reusability: Write once and reuse templates across multiple tasks.
How Do Ansible Templates Work?
Templates are stored as .j2 files and processed using Ansible’s template module, which replaces variables and applies logic before deploying the file to the target system.
Example Template File
nginx.conf.j2:server {
listen {{ nginx_port }};
server_name {{ nginx_server_name }};
location / {
root {{ nginx_root }};
index index.html;
}
}Example Playbook Using a Template
- name: Configure Nginx
hosts: webservers
vars:
nginx_port: 80
nginx_server_name: example.com
nginx_root: /var/www/html
tasks:
- name: Deploy Nginx configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: 0644This playbook:
- Substitutes variables (e.g.,
{{ nginx_port }}) with their defined values. - Saves the rendered configuration file to
/etc/nginx/nginx.conf.
Common Features of Templates
1. Variables:
Insert variables into templates for dynamic values. server_name {{ nginx_server_name }};
2. Conditionals:
Add logic to generate conditional content. {% if ssl_enabled %}
listen 443 ssl;
{% else %}
listen 80;
{% endif %}
3. Loops:
Use loops to iterate over lists or dictionaries. upstream backend {
{% for server in backend_servers %}
server {{ server }};
{% endfor %}
}
4. Filters:
Transform data with Jinja2 filters. upstream backend {
{{ backend_servers | join(' ') }}
}
5. Comments:
Use comments for clarity. # This is a comment
See also: VMware Tag Verification with Ansible
Benefits of Ansible Templates
- Dynamic Configuration:
- Efficiency:
- Reusability:
- Consistency:
Best Practices for Using Templates
- Use Meaningful Variable Names:
- Test Templates:
ansible-playbook command in check mode (--check) to validate templates before applying changes.
- Organize Template Files:
templates/ directory of your project or role.
- Secure Sensitive Data:
- Leverage Jinja2 Features:
See also: Can Ansible Be Used to Manage Windows Systems?
Conclusion
Ansible templates are a versatile tool for generating dynamic and reusable configuration files. By leveraging Jinja2 templating, you can adapt configurations to the needs of your automation workflows, ensuring consistency, efficiency, and customization.
Learn More About Ansible Templates
Basic Template Usage
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
mode: '0644'
become: true
notify: restart nginxSee also: Can Ansible Manage Windows? Complete Windows Automation Guide
Template with Variables
{# templates/nginx.conf.j2 #}
server {
listen {{ http_port | default(80) }};
server_name {{ server_name }};
root {{ document_root }};
access_log /var/log/nginx/{{ server_name }}-access.log;
error_log /var/log/nginx/{{ server_name }}-error.log;
location / {
proxy_pass http://{{ backend_host }}:{{ backend_port }};
}
}Loops in Templates
{# templates/hosts.j2 #}
{% for host in groups['webservers'] %}
{{ hostvars[host].ansible_host }} {{ host }}
{% endfor %}
{# templates/vhosts.conf.j2 #}
{% for site in websites %}
server {
listen 80;
server_name {{ site.domain }};
root {{ site.root }};
}
{% endfor %}Conditionals
{# templates/app.conf.j2 #}
[database]
host={{ db_host }}
port={{ db_port }}
{% if db_ssl | default(false) %}
ssl=true
ssl_cert={{ db_ssl_cert }}
{% endif %}
{% if ansible_distribution == 'Ubuntu' %}
socket=/var/run/mysqld/mysqld.sock
{% elif ansible_distribution == 'CentOS' %}
socket=/var/lib/mysql/mysql.sock
{% endif %}Filters
# Default values
port={{ app_port | default(8080) }}
# String manipulation
name={{ hostname | upper }}
slug={{ title | lower | replace(' ', '-') }}
# JSON/YAML output
config={{ app_config | to_nice_json }}
# Math
workers={{ ansible_processor_vcpus * 2 }}
memory={{ (ansible_memtotal_mb * 0.75) | int }}Template Module Parameters
| Parameter | Description |
|---|---|
src | Template file (relative to templates/) |
dest | Destination path on remote |
owner | File owner |
group | File group |
mode | File permissions |
backup | Create backup before overwriting |
validate | Validation command before deploy |
force | Replace even if dest exists |
Validate Before Deploy
- template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
become: trueTemplate Directory Structure
roles/webserver/
templates/
nginx.conf.j2
vhost.conf.j2
ssl.conf.j2
tasks/
main.yml
defaults/
main.ymlAdvanced: Macros
{% macro server_block(name, port, root) %}
server {
listen {{ port }};
server_name {{ name }};
root {{ root }};
}
{% endmacro %}
{{ server_block('app.example.com', 80, '/var/www/app') }}
{{ server_block('api.example.com', 8080, '/var/www/api') }}FAQ
template vs copy?
template processes Jinja2 variables and logic. copy transfers files as-is. Use template when you need dynamic content.
Where does Ansible look for templates?
templates/directory relative to the playbooktemplates/directory inside the role- The absolute or relative path you specify
How do I include another template?
{% include 'common-headers.j2' %}Can I template non-text files?
No — templates are for text files. Use copy for binary files.
Basic Template
- ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
mode: '0644'
become: true
notify: restart nginx{# templates/nginx.conf.j2 #}
server {
listen {{ http_port | default(80) }};
server_name {{ server_name }};
root {{ document_root }};
}Variables in Templates
# {{ ansible_managed }}
# Generated on {{ ansible_date_time.iso8601 }}
APP_NAME={{ app_name }}
APP_PORT={{ app_port }}
DB_HOST={{ db_host | default('localhost') }}
DB_PORT={{ db_port | default(5432) }}
LOG_LEVEL={{ log_level | upper }}Conditionals
{% if enable_ssl %}
listen 443 ssl;
ssl_certificate {{ ssl_cert_path }};
ssl_certificate_key {{ ssl_key_path }};
{% else %}
listen 80;
{% endif %}
{% if env == 'production' %}
error_log /var/log/nginx/error.log warn;
{% else %}
error_log /var/log/nginx/error.log debug;
{% endif %}Loops
{% for host in upstream_servers %}
server {{ host }}:{{ app_port }};
{% endfor %}
{% for user in allowed_users %}
AllowUser {{ user }}
{% endfor %}
{# With index #}
{% for vhost in virtual_hosts %}
# Virtual host {{ loop.index }}: {{ vhost.name }}
server {
server_name {{ vhost.domain }};
root {{ vhost.root }};
}
{% endfor %}Filters
{{ name | upper }}
{{ name | lower }}
{{ list | join(', ') }}
{{ dict | to_nice_json }}
{{ dict | to_nice_yaml }}
{{ value | default('fallback') }}
{{ text | regex_replace('old', 'new') }}
{{ password | password_hash('sha512') }}
{{ path | basename }}
{{ size_bytes | human_readable }}Practical: Application Config
{# templates/app.conf.j2 #}
[application]
name = {{ app_name }}
version = {{ app_version }}
environment = {{ env }}
debug = {{ (env != 'production') | lower }}
[database]
host = {{ db_host }}
port = {{ db_port }}
name = {{ db_name }}
pool_size = {{ (ansible_memtotal_mb / 256) | int | default(4) }}
[cache]
{% if redis_enabled | default(false) %}
backend = redis
url = redis://{{ redis_host }}:{{ redis_port }}/0
{% else %}
backend = memory
{% endif %}
[logging]
level = {{ log_level | default('INFO') | upper }}
file = /var/log/{{ app_name }}/app.logTemplate with Validation
- template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: "nginx -t -c %s"
become: true
- template:
src: sudoers.j2
dest: /etc/sudoers
validate: "visudo -cf %s"
become: trueTemplate Inheritance (Macros)
{# macros/server_block.j2 #}
{% macro server_block(name, port, root) %}
server {
listen {{ port }};
server_name {{ name }};
root {{ root }};
index index.html;
}
{% endmacro %}
{# main template #}
{% from 'macros/server_block.j2' import server_block %}
{{ server_block('app1.example.com', 80, '/var/www/app1') }}
{{ server_block('app2.example.com', 8080, '/var/www/app2') }}FAQ
template vs copy?
template processes Jinja2 (variables, loops, conditionals). copy transfers files as-is. Use template for dynamic content, copy for static files.
Where do templates go?
Convention: templates/ directory in your role or project root. Ansible looks there automatically.
Can I template a binary file?
No — template is for text files only. Use copy for binaries.
Related Articles
Category: troubleshooting