← Back
DevOps 5 min read

Jinja2: the engine that turns templates into infrastructure

Jinja2 is the Python template engine that lets Ansible generate a different configuration file for every environment. Understanding how it works is understanding how IaC handles variability.

When Ansible deploys an application across three different environments — development, staging, production — and each one needs a different configuration, it doesn’t write three files by hand. It uses a template and renders it with each environment’s data.

That template engine is Jinja2.

What Jinja2 is

Jinja2 is a Python library that takes a text file with markers in it and turns it into the final file, replacing those markers with real data. The output format can be anything: HTML, YAML, JSON, Nginx configuration files, shell scripts — anything that is text.

The idea is simple: the template defines the structure, the data defines the content.

from jinja2 import Template

template = Template("Hola, {{ nombre }}. Tu entorno es {{ entorno }}.")

resultado = template.render(nombre="Elias", entorno="producción")
print(resultado)
# Hola, Elias. Tu entorno es producción.

The syntax has three delimiters: {{ }} for variables, {% %} for control logic, and {# #} for comments. Everything else passes through to the output file untouched.

Logic inside the template

Jinja2 is not just variable substitution. It supports conditionals and loops directly inside the template.

{% if entorno == "produccion" %}
DEBUG = False
LOG_LEVEL = WARNING
{% else %}
DEBUG = True
LOG_LEVEL = DEBUG
{% endif %}

SERVIDORES_PERMITIDOS = [
{% for servidor in servidores %}
  "{{ servidor }}",
{% endfor %}
]

One template produces different configurations depending on the data it gets. No duplication, no per-environment files maintained separately.

Why it matters in DevOps

Ansible uses Jinja2 natively. Every configuration file Ansible deploys can be a Jinja2 template — the variables come from the inventory or the playbooks, and the final file that lands on the server already carries the right values for that specific host.

# Ansible playbook
- name: Configurar nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  vars:
    puerto: 8080
    workers: 4
# nginx.conf.j2
worker_processes {{ workers }};

server {
    listen {{ puerto }};
    server_name {{ ansible_hostname }};
}

The same template deploys to ten servers, each one with its own correct hostname. No manual editing.

This pattern — template + data = final file — is what lets IaC handle variability across environments without duplicating code. The infrastructure changes, the template doesn’t.

Beyond Ansible

Jinja2 shows up in more places than you would expect:

Flask and Django use it to render HTML server-side. Pages with dynamic data are Jinja2 templates that the framework renders with the request data.

Cookiecutter, the tool for generating projects from templates, uses Jinja2 to substitute the project name, the author, and the initial configuration across every file in the template.

Helm, the Kubernetes package manager, uses a syntax based on Go templates that shares the same conceptual logic as Jinja2 — YAML manifest templates with per-environment variables.

The pattern is the same in every case: a file with markers, data that fills them in, an engine that joins the two.

The minimum to get started

Three things to understand Jinja2:

{{ variable }} — inserts the value of a variable.

{% if %} / {% for %} — control logic inside the template.

| filters — transformations applied to a variable before it gets inserted.

{{ nombre | upper }}          {# ELIAS #}
{{ lista | join(", ") }}      {# a, b, c #}
{{ numero | default(0) }}     {# 0 if numero is None #}

Filters are what make Jinja2 more expressive than a plain find-and-replace. You can format dates, convert types, apply text transformations — all inside the template, with no extra logic in the code that renders it.

If you work with Ansible, you are already using Jinja2 whether you know it or not. Understanding the syntax explicitly is what stops the more complex playbooks from looking like magic.

Next · DevOps · 7 min Docker in production: what changes when the container actually matters Read next →