Every switch config is 90% identical. A Jinja2 template plus per-device variables generates consistent configs at scale — the heart of configuration management.
Why: device configs differ only in a few values (hostname, IPs, VLANs), so you write the config once as a template with placeholders and keep the per-device values as data — generating consistent configs and eliminating copy-paste drift. When: use templating whenever many devices share a config pattern. Where: this template + variables model is exactly how Ansible and modern config management work.
TEMPLATE (written once) + DATA (per device) = CONFIG
hostname {{ name }} name: SW1 hostname SW1
interface {{ uplink }} uplink: Gig0/1 interface Gig0/1
ip address {{ ip }} {{ mask }} ip: 10.0.0.1 ip address 10.0.0.1 ...
Change the template once -> every device's config updates consistently.Why: Jinja2 fills a template with a dictionary of variables to produce the final config text — the same engine used across the Python ecosystem. When: render one config per device from its variables, then push it with Netmiko. Where: loops and conditionals in the template handle lists (many VLANs) and optional features cleanly.
# pip install jinja2
from jinja2 import Template
template = Template("""hostname {{ name }}
!
{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name }}
{% endfor %}
""")
config = template.render(
name="SW1",
vlans=[{"id": 10, "name": "Sales"}, {"id": 20, "name": "Eng"}],
)
print(config)Why: combining templating with Netmiko is the whole workflow — data in, rendered config out, pushed to the device — repeatable for every device in an inventory. When: this is the pattern you scale to a fleet: one template, an inventory of variables, a loop. Where: render to a file first so the change is reviewable in a pull request before it touches a device.
from jinja2 import Template
from netmiko import ConnectHandler
tmpl = Template(open("templates/switch.j2").read())
for dev in inventory: # list of per-device variable dicts
config = tmpl.render(**dev["vars"]) # data -> config text
with ConnectHandler(**dev["conn"]) as conn:
conn.send_config_set(config.splitlines())
conn.save_config()
print(dev["vars"]["name"], "configured")