Jinja2 Templates for Network Config
Jinja2 Templates for Network Config
Introduction
As your network grows from a handful of devices to dozens or hundreds, manually writing configuration files for each switch and router becomes unsustainable. A single VLAN change across fifteen leaf switches means editing fifteen separate configuration snippets, and every manual edit is a chance for a typo that brings down a segment. Jinja2 templating solves this problem by letting you define a configuration pattern once and then automatically generate device-specific configs from structured data.
In this lesson you will learn how Jinja2 templates integrate with Ansible to produce network configurations at scale. By the end, you will be able to:
- Explain what Jinja2 templates are and why they matter for network automation.
- Write a Jinja2 template that loops through variable data to build VLAN configurations.
- Understand how Ansible roles, variable files, and templates connect to form a complete automation workflow.
- Distinguish between legacy and resource modules for NX-OS and know when to fall back to the config or command modules.
Key Concepts
What Is Jinja2?
Jinja2 is a text-based templating engine used by Ansible. Instead of hard-coding device-specific values such as VLAN IDs, VNI numbers, or IP addresses directly into a playbook task, you place placeholders inside double curly braces — {{ variable }} — and Jinja2 fills them in at run time from your variable files.
Ansible Roles and Directory Structure
Ansible encourages you to organize automation work into roles. Each role has its own folders for tasks, templates, and variables. When you build a VXLAN fabric, for example, the directory layout looks like this:
| Directory | Purpose |
|---|---|
roles/overlay/tasks/main.yml | The task file that calls the template and pushes config |
roles/overlay/templates/vlans.j2 | The Jinja2 template that generates VLAN payload |
group_vars/leafs.yml | Variables shared by all leaf switches |
host_vars/10.15.1.13.yml | Variables unique to a single leaf switch |
This separation means your automation logic lives in the task file, your configuration pattern lives in the template, and your per-device or per-group data lives in variable files. Change the data and the config follows automatically.
Variable Quoting Rule
One detail that trips up many beginners: if a YAML value starts with {{ }}, you must wrap the entire expression in quotes. Ansible parses YAML before it evaluates Jinja2 expressions, and an unquoted opening brace is invalid YAML. For example:
# Correct
config: "{{ nxos_vlans | from_yaml }}"
# Incorrect — will cause a YAML parse error
config: {{ nxos_vlans | from_yaml }}
Legacy Modules vs. Resource Modules
Ansible provides two families of modules for configuring NX-OS devices. Understanding the difference helps you choose the right approach when templates alone are not enough.
| Attribute | Legacy Modules | Resource Modules |
|---|---|---|
| Consistency across platforms | Inconsistent across different network devices | Consistent across different network devices |
| Handling multiple items | Requires task loops for more than one configuration item | Can leverage task loops or Jinja2 templating for config blocks |
| State management | Simple states: present or absent | Introduces new states for Ansible to be the source of truth |
Resource modules are the preferred choice for modern automation because they let Ansible act as the source of truth for your network configuration, with richer state options beyond simple present/absent.
How It Works
The Jinja2 workflow in Ansible follows a clear sequence: define your data, write a template, render the template into a configuration payload, and push that payload to the device.
Step 1 — Define the Data
Variable data is stored in YAML files. Group-level variables apply to every device in a group, while host-level variables apply to a single device. Here is an example of a group variable file for leaf switches:
# group_vars/leafs.yml
networks:
- vrf_name: AnsibleVRF
vlan_name: AnsibleNet1
vlan_id: 101
vni_id: 10101
ip: 10.1.101.1/24
- vrf_name: AnsibleVRF
vlan_name: AnsibleNet2
vlan_id: 102
vni_id: 10102
ip: 10.1.102.1/24
A host variable file adds device-specific values such as the hostname:
# host_vars/10.15.1.13.yml
hostname: L1
Step 2 — Write the Jinja2 Template
The template uses a {% for %} loop to iterate over the networks list and produce one block of YAML output per network entry:
# roles/overlay/templates/vlans.j2
{% for network in networks %}
- vlan_name: {{ network.vlan_name }}
vlan_id: {{ network.vlan_id }}
vni_id: {{ network.vni_id }}
{% endfor %}
When Ansible renders this template against the variable data above, it produces a list of two VLAN definitions — one for VLAN 101 and one for VLAN 102 — without you writing either definition by hand.
Step 3 — Render and Push
The task file in the role ties everything together. It first uses ansible.builtin.set_fact with the lookup('template', ...) function to render the Jinja2 template into a variable. Then it passes that variable to the cisco.nxos.nxos_vlans resource module:
# roles/overlay/tasks/main.yml
- name: Generate VLAN Config Payload
ansible.builtin.set_fact:
nxos_vlans: |
"{{ lookup('template', 'vlans.j2') }}"
- name: Configure VLANs
cisco.nxos.nxos_vlans:
config: "{{ nxos_vlans | from_yaml }}"
state: merged
The state: merged directive tells Ansible to merge the generated configuration with whatever already exists on the switch. This is one of the richer state options that resource modules provide.
Step 4 — The Playbook Orchestrates Roles
A top-level playbook assigns roles to device groups from the inventory:
# vxlan.yml
- hosts: spines, leafs
gather_facts: false
roles:
- role: common
- role: underlay
- hosts: leafs
gather_facts: false
roles:
- role: overlay
The inventory file maps group names to device IP addresses:
# inventory.yml
all:
children:
spines:
hosts:
10.15.1.11:
10.15.1.12:
leafs:
hosts:
10.15.1.13:
10.15.1.14:
10.15.1.15:
You execute the entire workflow with a single command:
ansible-playbook -i inventory.yml vxlan.yml
Ansible reads the inventory, matches hosts to plays, loads group and host variables, renders templates, and pushes configurations to every device in the correct order.
Configuration Example
Fallback: The nxos_config Module
Sometimes there is no dedicated resource module for the feature you need to configure. In that case, the cisco.nxos.nxos_config module lets you pass raw CLI lines directly:
- name: Configure PIM Anycast RP
cisco.nxos.nxos_config:
lines:
- "ip pim anycast-rp {{ s1_loopback1 }} {{ s1_loopback0 }}"
- "ip pim anycast-rp {{ s2_loopback1 }} {{ s2_loopback0 }}"
save_when: modified
Notice the Jinja2 variables inside the CLI lines — even when you drop down to raw config mode, you still benefit from templating. The save_when parameter controls when a copy running-config startup-config is performed:
| save_when Value | Behavior |
|---|---|
always | Copy always |
modified | Copy only if config changed since last save |
changed | Copy only if the task made a change |
never | Never copy |
Fallback: The nxos_command Module
For operational commands such as show outputs, use cisco.nxos.nxos_command:
- name: Get Show Commands
cisco.nxos.nxos_command:
commands:
- show version
- show ip ospf neighbor
- show ip pim neighbor
This module also supports interactive prompts. When a command asks for a username and password, you can supply answers automatically:
- name: Misc Commands
cisco.nxos.nxos_command:
commands:
copy ftp://nxos.bin bootflash:
prompt:
- "Username:"
- "Password:"
answer:
- <username>
- <password>
Best Practice: Use resource modules whenever one exists for the feature you are configuring. Fall back to
nxos_configfor CLI-only features andnxos_commandfor show or file-transfer commands. This layered approach keeps your automation as structured as possible while still covering edge cases.
Real-World Application
Data Center Fabric Deployment
The most common production use case for Jinja2 templates is deploying VXLAN EVPN fabrics. A data center may have two spine switches and dozens of leaf switches. Every leaf needs the same set of VLANs, VNIs, and VRF mappings, but with different loopback IPs and hostnames. By placing shared network definitions in group_vars/leafs.yml and device-unique values in individual host_vars files, a single ansible-playbook run configures the entire fabric consistently.
Branch Office Rollouts
The reference lab topology includes a core XR router, branch Cat8Kv routers, and NX-OS switches. Jinja2 templates work the same way here: define interface IPs and OSPF areas in variable files, write templates for interface and routing configuration, and deploy across all branch devices with one playbook. When a second branch is added, you reuse the same playbooks with new variable files — no logic changes required.
Ansible Configuration File Lookup Order
When running playbooks, Ansible searches for its configuration file ansible.cfg in a specific order:
- The path set in the
ANSIBLE_CONFIGenvironment variable - The current working directory
- The user's home directory
/etc/ansible
Important: Always execute your playbooks from the directory that contains your project's
ansible.cfg. Running from the wrong directory can cause Ansible to pick up a different configuration file, leading to unexpected behavior such as wrong inventory paths or missing module settings.
Design Considerations
- Indentation matters. YAML is whitespace-sensitive. When editing playbooks, use an editor that shows vertical indentation guides — a single extra or missing space can break a playbook.
- Separate data from logic. Keep your variable files clean and your templates focused on structure. This makes it easy to onboard new devices by adding a variable file rather than editing automation logic.
- Use roles for reusability. Roles like
common,underlay, andoverlaycan be mixed and matched across playbooks. A branch deployment playbook and a data center playbook can share the samecommonrole.
Summary
- Jinja2 templates let you define a configuration pattern once and generate device-specific configs by looping through structured variable data with
{% for %}blocks and{{ variable }}placeholders. - Ansible roles organize tasks, templates, and variables into reusable units — separating what to do, how to do it, and data to do it with.
- Resource modules like
cisco.nxos.nxos_vlansare preferred over legacy modules because they provide consistent behavior across platforms and richer state management including themergedstate. - When no resource module exists,
nxos_confighandles raw CLI configuration andnxos_commandhandles show commands and interactive prompts. - Always run playbooks from the correct directory so Ansible finds the right
ansible.cfg, and always quote YAML values that start with{{ }}.
In the next lesson, we will look at playbook optimization techniques including import_playbook for reusing playbooks across multiple sites and encrypting sensitive credentials with Ansible Vault.