DEV Community

Cover image for Terraform finally met switches: Cisco Network as Code with ACI just works
Jan Pokorný
Jan Pokorný

Posted on

Terraform finally met switches: Cisco Network as Code with ACI just works

Every network engineer wants to automate their network. Automation is our pride. Working automation is proof of good architecture — and in large setups, an operational necessity. Yet I've always felt that switch vendors aren't exactly helping us.

Networking is a conservative world. Every time I dove into automation, I ended up frustrated by how far behind it is. First, there are the archaic protocols that nobody truly understands and that — at least everywhere I've worked — never delivered in practice: SNMP in write mode, NETCONF, RESTCONF, screen-scraping over SSH. (Yes, OpenConfig and gNMI exist; if your vendor mix supports them well, I envy you.) Right next to them stands a long parade of revolutionary proprietary management systems from the vendors themselves. The trouble with those is always the same: they're heavyweight, they integrate poorly with anything else, and above all they have the lifespan of a mayfly. Just for HP switches, I'm running out of fingers on one hand counting how many times their primary management tool has changed.

Ansible and the REST APIs appearing on switches looked like a small ray of hope. But even there, disappointment came with time. Ansible modules typically covered only some parts of the configuration — full coverage was never on the table. Partly because modules were missing, partly because Ansible simply isn't the right tool for this domain: there's no state, no plan/diff before you push, and deleting things is entirely on you — which is exactly what makes it unmaintainable at scale. The final nail in the coffin is opening GitHub and seeing that the last commit to the relevant collection was five years ago.

So in the end, we all land back at SSH and pushing CLI commands. Hand on heart — isn't that so? Thanks to netmiko we can more or less reliably shove config onto switches and call it automation. But when someone asks whether we can also change a config, or — heaven forbid — safely delete one, we change the subject.

From Ansible fan to Terraform convert

I used to be a big Ansible fan. I built some genuinely nice, complex projects with it. I even wrote my own modules. Then I got pulled into a cloud project that required Terraform. The moment I tried it, I understood: for infrastructure, Terraform is the hammer. And from that moment on, every problem I saw was a nail. From day one I kept thinking: I wish I could manage switches like this.

Then the opportunity came. We decided to upgrade the network in our datacenters, and Cisco ACI was one of the contenders. I noticed Cisco offers Terraform-based management, so I tested it during the PoC. It worked well, and the conclusion was that it could cover our day-to-day operations and unlock automation.

Long story short: today, 99% of our entire ACI configuration is driven by Terraform. From NTP to L3Outs.

What Cisco Network as Code actually is

Cisco NaC is essentially ready-to-eat Terraform, prepared directly by Cisco. And credit where it's due — Cisco did a phenomenal job here (at least for ACI). We run it on ACI 6.1(5e) with NaC module version 2.0.0.

Why does it work so well? In my view, two cornerstones:

1. APIC. Managing one central controller with a high-quality API is orders of magnitude easier than managing dozens or hundreds of individual switches. Terraform talks to a single API with one coherent view of the fabric, not to a fleet of boxes.

2. Separation of data from code. As a user, you practically don't need to know Terraform at all. You don't write Terraform — you write a data structure (YAML), which the Terraform module reads and assembles itself into whatever is needed. This isn't a Cisco invention — Google does something similar in their Fabric FAST framework for landing zones — but Cisco executed it really well. A complete beginner can have a working project within a few hours.

Look how simple the Terraform part is. This is essentially the whole thing:

terraform {
  required_providers {
    aci = {
      source = "CiscoDevNet/aci"
    }
  }
}

provider "aci" {
  username = var.aci_user
  password = var.aci_password
  url      = var.aci_url
}

module "aci" {
  source  = "netascode/nac-aci/aci"
  version = "2.0.0"

  yaml_directories = ["data/**"]

  manage_access_policies    = true
  manage_fabric_policies    = true
  manage_pod_policies       = false
  manage_node_policies      = true
  manage_interface_policies = true
  manage_tenants            = true
}
Enter fullscreen mode Exit fullscreen mode

It really just says how to connect to the APIC, which configuration sections Terraform should manage, and where to load the data model from. You flip on only the sections you want Terraform to own — we leave pod policies at their day-0 defaults, which is pretty much the missing 1 % from the claim above.

The data model itself is plain YAML. A bridge domain looks like this:

---
apic:
  tenants:
    - name: company
      bridge_domains:
        - name: d2-dev-lan-1_1201
          vrf: dc2-l3-fix
          unicast_routing: false
          description: General LAN network
Enter fullscreen mode Exit fullscreen mode

And the best part: you can split the data model into as many small files as you like and build any directory structure that suits your team. Terraform merges everything into one big YAML document and processes that. Our structure looks like this:

├── docs
├── iac
│   └── data
│       ├── access_policies
│       │   ├── interface_policies
│       │   └── switch_policies
│       ├── fabric_policies
│       ├── interface_policies
│       │   ├── 101_102
│       │   ├── 103_104
│       │   ├── 105_106
│       │   └── ...
│       ├── node_policies
│       └── tenants
│           ├── mgmt
│           ├── company
│           │   ├── application_profiles
│           │   │   └── endpoint_groups
│           │   ├── bridge_domains
│           │   └── vrfs
│           └── lab
└── scripts
Enter fullscreen mode Exit fullscreen mode

Cisco's model covers, I dare say, 100% of the configuration — I haven't yet hit anything I couldn't find — and the documentation is perfectly readable.

Terraform is not the automation. It's the key to it.

Managing APIC with Terraform is not automation by itself. It's the key that unlocks it.

It unlocks GitOps. Your complete configuration is now text, living in git. You have every change and the complete history — from the day the fabric was born to the last update. You know who made each change and based on what. (We had the luxury of a greenfield deployment — our fabric was born straight into git. Adopting NaC on an existing brownfield fabric is possible, but it's a story of its own.)

It unlocks CI/CD and real change management. Suddenly, changes aren't made by a specific engineer clicking in a GUI — they're made by a pipeline. Every change is a pull request that someone has to approve. In the PR you see exactly what is being proposed, and exactly what Terraform plans to do on the APIC side (terraform plan is posted right into the PR as a comment). Review rules are explicit and enforced. And before anything gets applied, the pipeline takes an APIC configuration snapshot. No snapshot, no apply. Period.

Pull request with Terraform plan posted as a comment

Plan detail: Plan: 11 to add, 0 to change, 0 to destroy

Pipeline run: plan → snapshot → apply

NetBox as the source of truth

Now for the actual automation. We record absolutely everything in NetBox — it is our source of truth. Devices, every single cable, VLANs, prefixes, IP addresses, VRFs, virtualization clusters… everything is diligently documented there.

Since the ACI side is now just YAML files, nothing was easier than adding a few custom fields to NetBox and — with a handful of prompts to Claude — building a script that pulls data from the NetBox API and updates the YAML files. New network segment? Add a record to the central documentation, and within minutes there's a pull request waiting for approval. Approve, and it's deployed.

VLAN object in NetBox with ACI custom fields

Or skip the scripts — bring an AI agent

Maybe writing Python scripts isn't trendy anymore. Maybe it isn't even necessary. The next step is an AI agent. If there's one thing AI agents are unbeatable at, it's working with text — and what better task could one get than generating YAML from a work order?

"I've added server company-server-01 to NetBox. Prepare the configuration to connect it to ACI."

The agent knows NetBox exists. It can look up which rack the server is in and which leaf switches live in that rack. It can check what cable type is planned. It knows our port-allocation rules and the ACI object naming convention. This is a trivial task — even a small model handles it comfortably. And there's nothing to be afraid of: it does exactly the same thing a human would. It opens a pull request for approval. There's still a human in the loop.

AI agent session preparing ACI YAML from NetBox data

The risks (and how to handle them)

It wouldn't be fair to end without the downsides. Cisco NaC has a few, and they're manageable.

The growing state file

Every new section of configuration means a bit more time for Terraform to compute its plan. In the beginning, Terraform was snappy; as the project grows, the full cycle takes longer and longer. This isn't an ACI or NaC problem — it's a classic Terraform problem, and the solution is classic too: split the project into sub-projects per configuration section. If I'm working on access policies, there's no need to recompute state for tenants, and vice versa — and when both change, they can run in parallel. Cisco NaC already divides the configuration tree into six sections (you can see them as the manage_* flags in main.tf above), which is the natural place to cut. Splitting below that level feels risky to me. We're not that big and I'm confident we'll manage all-in-one — but for huge setups, this would be a real topic.

Drift — and the blast radius of an apply

The elephant in the room: what happens when someone changes something in the GUI? Terraform doesn't know about it — until the next plan, which flags it as drift, and the next apply, which reverts it. That's either your best friend or a production incident, depending on whether the GUI change was a mistake or an emergency fix someone forgot to backport.

We handle it with two rules. First, configuration changes go through pull requests only — the GUI is for reading and troubleshooting. Second, the pipeline runs terraform plan on a schedule, so any drift shows up as an alert within hours, not months later during an unrelated change.

The same mechanism cuts the other way, too: a bad PR can produce a plan that destroys objects — and on a production fabric, X to destroy can mean EPGs going away along with the traffic in them. This is exactly why the plan is posted into the PR: the reviewer sees the add/change/destroy counts before approving, and the pre-apply APIC snapshot is the last resort. An automated guard that blocks plans with unexpected destroy counts would be a sensible next hardening step — reviewers are human.

No referential integrity between objects

Thanks to the data/code separation, the links between objects are gone. Nothing stops you from referencing a policy that doesn't exist — typically a typo in a name. With hand-written Terraform and its resource references, this couldn't happen. It's a small thing, but worth knowing. There's a possible fix: nac-validate, a CLI that validates the YAML against a schema and runs semantic rules (written in Python) that should catch dangling references before the plan even runs. I'll be honest — I only gave it a quick try, it didn't really grab me, and we don't use it for now. Your mileage may vary, so it's worth a look. Until then, an AI agent doing a pre-push textual consistency check finds this kind of problem effortlessly, and worst case, the APIC will raise a fault and tell you anyway.

What if it all falls apart?

What if, for whatever reason, you can't continue with Terraform? Then nothing stops you from simply throwing it away and continuing another way. The objects Terraform created are ordinary ACI objects — you just start managing them differently. There's no lock-in in the data. Support-wise, one nuance worth knowing: the underlying ACI Terraform provider is fully TAC-supported, while the NaC modules themselves are open source maintained by Cisco, with Cisco Professional Services behind them for customization.

Closing thoughts

For years, "network automation" meant heroically wrapping duct tape around SSH. With ACI and Cisco Network as Code, for the first time I feel like we're doing infrastructure-as-code on the network with the same comfort the cloud folks have had for a decade: declarative data model, git history, PR-based change management, CI/CD, and a source of truth feeding it all.

If you're running ACI and still clicking in the GUI — try the PoC. It took us a few hours to get the first working project, and we never looked back.

Happy to answer questions about our setup — pipelines, NetBox integration, or the AI agent workflow. Drop them in the comments.

Top comments (1)

Collapse
 
ugur_ersoy_2762c05b9829e8 profile image
Ugur Ersoy •

Thanks for sharing this, it's really valuable information. We also wanted to use nexus-as-code for our existing fabrics, but it seems to require a greenfield environment, as it wants to deploy and manage all the configurations itself. Did you take the same approach, or are we missing something?