From 584 lines of hand-typed IOS to one YAML edit per change
Six devices. 584 lines of config. Zero typed by hand.
Well... the second time. 😅
Why I did this
A while back I built a full enterprise campus network for the CCNA in Cisco Packet Tracer. Core, distribution and access layers, OSPF, HSRP, EtherChannel, NAT, ACLs, DHCP snooping, Dynamic ARP Inspection, IPv6, a wireless LAN controller. The whole thing.
Every single line was typed into a CLI. By me. One device at a time.
So what's the issue? The issue is that every job posting I read says "network automation", and I had never automated anything on this lab. Saying I know automation when all I've done is type commands felt wrong.
So I rebuilt the lab as code. Here is what that looked like, what broke, and what I learned.
The idea in one sentence
Describe the network as data, and let a template write the config.
If you've ever done a mail merge, you already get it. You have a letter with blanks like Dear {{ name }} and a spreadsheet of names. Merge them and you get 100 personalized letters.
Here it's the same thing:
- The spreadsheet = YAML files describing each device (VLANs, IPs, HSRP, OSPF)
- The letter = Jinja2 templates that look almost exactly like Cisco config
- The merge button = Ansible
1. Describe the network as data
Here is VLAN 10 on one of my distribution switches, written as YAML:
- name: Vlan10
ipv4: 10.1.0.2 255.255.255.0
ospf: true
ospf_passive: true
hsrp: {group: 2, vip: 10.1.0.1, priority: 105, preempt: true}
acl_in: OfficeA_to_OfficeB
No interface, no standby, no ip helper-address. Just facts about the interface.
Settings every device shares (DNS, NTP, syslog, SSH hardening) live in one file. Change the syslog server once and every device picks it up.
2. Let the template write the config
That YAML gets rendered into this:
interface Vlan10
description PCs
ip address 10.1.0.2 255.255.255.0
ip helper-address 10.0.0.76
standby version 2
standby 2 ip 10.1.0.1
standby 2 priority 105
standby 2 preempt
ip access-group OfficeA_to_OfficeB in
The description comes from the VLAN name. The DHCP relay gets added because it's a distribution switch. I didn't type any of it.
It also adds lines to other parts of the config:
-
network 10.1.0.2 0.0.0.0 area 0andpassive-interface Vlan10under OSPF -
spanning-tree vlan 10,99 priority 0, because this switch is the HSRP active gateway for VLAN 10, so it should also be the STP root
Pro Tip: That last one is my favorite. "STP root follows HSRP" is a design rule you learn for the CCNA. Now it's enforced in code, so the two can never drift apart.
3. The plot twist: Packet Tracer says no
My plan was simple. Write the templates, then let Ansible SSH into each device and push the config.
Packet Tracer does not accept SSH connections from outside the app. At all. lol.
So Ansible can't push to my lab. Pretty big problem for an automation project!
Instead of giving up, I changed the question from "can I push it?" to "can I prove the templates are correct?"
4. Proving it: the drift check
I already had the hand-built configs from the original lab saved in the repo. So I wrote a checker that compares the configs the templates generate against those originals.
The result:
| Device | Role | Lines rendered | Status |
|---|---|---|---|
| R1 | Edge router | 119 | ✅ MATCH |
| CSW1 | Core | 81 | ✅ MATCH |
| DSW-A1 | Distribution | 125 | ✅ MATCH |
| DSW-B1 | Distribution | 120 | ✅ MATCH |
| ASW-A1 | Access | 74 | ✅ MATCH |
| ASW-B1 | Access | 65 | ✅ MATCH |
6 of 6 devices, zero drift. The templates recreate the lab I built by hand.
Note: It's not a plain text diff. Cisco doesn't care what order most commands are in, but it does care about the order of ACL entries. So the checker ignores order everywhere except inside ACLs.
To make sure it wasn't just saying "looks good!" to everything, I broke a config on purpose. I changed an HSRP priority, swapped two ACL lines and deleted a VLAN. It caught all three.
It also caught a real mistake in my original build: two access switches had enable secret and username admin typed twice. IOS doesn't mind, but the generated configs are clean. Automation 1, manual typing 0.
5. Where it gets good: adding a VLAN
Let's say I need a Guest VLAN. By hand, that means:
- Create the VLAN
- Create the SVI with an IP, DHCP relay and HSRP
- Add the VLAN to every trunk
- Update STP root priorities
- Add the network to OSPF and make it passive
Forget step 3 on one trunk and guests can't get an IP. Forget step 5 and it's a routing mystery at 2 AM.
With the templates, it's 7 lines of YAML:
vlans:
- {id: 40, name: Wi-Fi}
+ - {id: 50, name: Guest}
+ - name: Vlan50
+ ipv4: 10.7.0.2 255.255.255.0
+ ospf: true
+ ospf_passive: true
+ hsrp: {group: 5, vip: 10.7.0.1}
And those 7 lines produce 15 correct IOS changes: the VLAN, the SVI with HSRP and DHCP relay, 4 trunks, the STP priority, and 2 OSPF lines. You can't forget a step when there's only one step.
6. Safety rails (because networks are scary)
The deploy playbook is ready for real gear, and it's built to be hard to misuse:
-
Dry run by default. It shows what each device would receive. You have to type
apply=trueto actually push. - No passwords in Git. Real secrets come from an encrypted Ansible Vault file. If a placeholder password is still in the config, it refuses to push.
- Backup first. Every device's running config is saved before anything changes.
- One device at a time. If one fails, the rollout stops.
7. A green checkmark on every commit
GitHub Actions runs on every push:
✅ YAML lint
✅ Ansible lint (strictest "production" profile)
✅ Syntax check on every playbook
✅ Render all configs + drift check
If someone (me) edits the YAML and breaks something, the badge turns red before it ever gets near a switch.
What I learned
- Automation isn't about typing faster. It means every change gets reviewed, can be repeated, and provably matches the design.
- Prove it before you push it. The drift check was worth more than the deploy playbook. It's the part that makes me trust everything else.
- Be honest about what's tested. Deploy and verify haven't touched a live device yet, and the README says so. I'd rather say that in an interview than get caught overselling.
What's next
Rebuilding a few devices in Cisco Modeling Labs (which has real IOS and real SSH) so I can run the full push → verify → backup loop for real. When that works, expect a part 2. 👀
That is it. It's not perfect, and there's a good chance a network engineer reading this has a better way to do half of it. If so, please tell me!
If you want to dig into the code, here's the repo:
GitHub: https://github.com/VibhavChennamadhava/CCNA-Enterprise-Campus-Mega-Lab
Question for the comments: have you automated a home lab? What did you use: Ansible, Nornir, Netmiko, something else? I'd love to hear what worked (and what didn't)!
Questions or comments? Love to hear from you!
Top comments (0)