Conclusion
You've reached the end of the Network as Code for IOS XE lab - well done.
What you've accomplished
Core skills
- YAML configuration - wrote declarative network configurations in human-readable YAML
- Terraform workflow - used
terraform init,plan,apply, anddestroyend-to-end - Configuration hierarchy - applied settings at global, device-group, and device levels with proper precedence
- Schema validation - caught errors pre-deployment with
nac-validate - CI/CD automation - ran GitLab pipelines to validate, plan, and deploy changes
Configuration patterns practiced
- Global - login banners, system hostnames applied everywhere
- Device group - ACLs applied to access switches only
- Device-specific - per-device settings (loopbacks, BGP, host-specific ACLs)
- Variables - reusable values across the hierarchy
Optional skills (if you reached them)
- Template types:
model,file, andcli - Post-change validation with
nac-testand Robot Framework - Pipeline extensions: integration tests + idempotency checks
- GitOps guardrails: branch protection and merge-request workflows
Key takeaways
- Declarative over imperative - define what you want; let the module figure out how.
- Version-control everything - every change has an author, a reviewer, and a rollback path.
- Automate the workflow - pipelines enforce consistency and make failure visible.
- Validate before deploy -
nac-validatecatches syntax and schema errors before they reach a device. - Test after deploy -
nac-testconfirms intent actually landed on the wire.
How this scales from four devices to a hundred (or a thousand)
The patterns you just used were chosen because they scale. A fleet of 100 branches doesn't change the workflow; it just changes the input:
- The YAML shape is identical. One
devices/*.nac.yamlper device regardless of fleet size. Adding branch 101 is creating one file, not restructuring anything. Shared configuration (a new NTP server, a security policy update) still lives in exactly one file (global/or agroups/file) and propagates via the same merge precedence. - Exceptions stay local. If branch 47 needs a one-off setting that
differs from every other branch, it goes in
devices/branch-47.nac.yamland overrides the global default for that one device. Nothing else in the project moves. The merge model is what makes "the exception that proves the rule" a one-file change instead of a refactor. - Blast radius is controllable.
managed_devices = ["branch-47"](Task 12) or the equivalentIOSXE_SELECTED_DEVICESenv var scopes aterraform applyto a named subset. Combined with CI pipelines gating on code review (Task 15), you can stage rollouts - apply to three canary branches first, observe, then expand. The 4-device lab version of this is runningterraform applyagainst all 4; the 100-device production version is the same command with a subset filter. - State is the authoritative record. One
terraform.tfstate(or one pipeline-managed state backend, for a team) captures what each of the 100 devices currently has. Drift detection, audit trails, and "what did we actually change last Tuesday" answers all come from that state plus git history.
What you wrote for 4 devices is the same YAML you'd write for 400. The
module, the merge semantics, the CI pipeline shape, and the verification
loop stay identical; the data/devices/ directory just gets bigger.
Try at home
A few small extensions make great follow-ups once you're back at your own environment:
- Default gateway for host networks - extend
devices/border.nac.yamlto add aGigabitEthernet3interface with IP192.168.100.1/24so the host networks can route to the ISP. Validate by pinging8.8.8.8fromhost01/host02via the CML console. (This is also the "Advanced Challenge" in Task 08 if you finished early during the session.) - More variables, more precedence - introduce a
${DNS_DOMAIN}or${MGMT_VLAN}variable at the global level and override it on one device to see precedence in action. - Your own validation rules -
nac-validateis extensible. Write a rule that enforces a naming convention or flags a forbidden configuration (for example, any ACL entry withaction: permitonany). - Verify via data sources, not SSH - the
terraform-provider-iosxeships ~120 data sources that read operational state. Instead of SSHing in and runningshow ip bgp summary, define adata "iosxe_bgp_neighbor"resource and assert the state in a Terraformcheckoroutput. Same answer, fully programmatic - useful for letting another part of your IaC codebase condition on device state. - Brownfield import with
nac-tool- pointnac-toolat one of your existing IOS XE devices at home and let it generate the equivalent NaC YAML. This is the fastest way to see how much of your current running-config maps cleanly to NaC's data model (and where you'd need to fall back toclitemplates).
Continue your journey
Core documentation and code
- Network as Code documentation - the authoritative docs site; start with Guides → Concepts if you want to go deeper on merge semantics, defaults precedence, and variable scope resolution
terraform-iosxe-nac-iosxe- the module used in this lab (Apache 2.0). File issues via its GitHub Issues page.terraform-provider-iosxe- the IOS XE Terraform provider. Full resource and data-source reference at registry.terraform.io/providers/CiscoDevNet/iosxe.- NetAsCode on GitHub - the full org: modules, tools, and data models for every supported Cisco platform (IOS XE, SD-WAN, ACI, Catalyst Center, Nexus Dashboard, Meraki, etc.)
Sibling tools worth knowing about
This lab uses nac-validate (Task 10) and nac-test (Task 11). The NaC toolchain ships a few more:
nac-tool- brownfield import. Point it at a running device and it writes out the equivalent NaC YAML. This is the bridge between "existing network" and "Network as Code adoption" - no need to start from scratch.nac-api- REST API over NaC configs. Useful for custom integrations.nac-collector- gather deployed configs from the field into a central store.
Share your feedback
If you hit something confusing, broken, or wished-for-but-missing while working through the lab, we'd love to hear about it. Two ways to contribute:
- Open a GitHub issue on this repo. Great for anything reproducible - a step that doesn't work as documented, a broken link, a factual error, a suggestion for a new task. Screenshots and commit SHAs of the version you hit welcome.
- Email the authors directly (below). Best for higher-level feedback: what confused you about the flow, what you wish the lab had covered, what would have made a specific concept click sooner. Specific beats general; "the Task 08 BGP verification was unclear because X" is 10x more useful than "Task 08 was confusing."
If you delivered this lab at an event and there's an event-specific feedback channel (Cisco Live session survey, TechAdvantage evaluation, customer workshop retrospective), please use that too - the authors read those but they don't reach the maintainers of this repo directly.
Thank you
Thanks for spending four hours with us. If you have questions or feedback after the session, please reach out:
← Previous: Task 15 - Merge request workflow