Terraform for Network Engineers¶
Terraform describes infrastructure in declarative files, compares them with what exists, and makes the difference. Network teams meet it first in cloud networking (VPCs, transit gateways, firewalls), then in controllers such as ACI and Meraki, and increasingly on IOS XE devices. This article explains what infrastructure as code means for a network, how providers and state work, the plan/apply workflow, and walks through a small working example.
What IaC means for a network¶
With the CLI, the device's running config is the only record of intent. With infrastructure as code:
- The desired state lives in text files in Git.
- A tool reads those files, reads the real state, and computes the changes.
- Review happens on the files before anything is touched.
- The same files can build a lab, a staging copy and production.
Terraform is declarative. You write there is a VPC with this CIDR and these subnets, not create a VPC, then create subnet 1. Terraform works out the order from references between resources. Ansible and Netmiko are procedural by comparison: they run steps against a device that already exists. Terraform fits things that are created and destroyed through an API, which describes cloud networks and controllers well and traditional box-by-box CLI devices less well.
Core concepts¶
graph LR
CODE["Configuration<br/>*.tf files"] --> PLAN["terraform plan"]
STATE["State file"] --> PLAN
REAL["Real infrastructure<br/>via provider API"] --> PLAN
PLAN --> APPLY["terraform apply"]
APPLY --> REAL
APPLY --> STATE
Providers are plugins that talk to an API. You declare which ones you need and Terraform downloads them on terraform init. Providers relevant to network work:
| Provider | Manages | Notes |
|---|---|---|
hashicorp/aws, hashicorp/azurerm, hashicorp/google |
Cloud VPCs and VNets, routes, security groups, gateways | Mature, widely used |
CiscoDevNet/aci |
ACI tenants, VRFs, bridge domains, EPGs, contracts via the APIC | Official Cisco DevNet provider |
CiscoDevNet/iosxe |
IOS XE devices over RESTCONF | Needs RESTCONF enabled; resource coverage grows with releases |
| Meraki provider | Meraki Dashboard organisations, networks, VLANs, SSIDs | Check the registry for the currently maintained provider |
Search the Terraform Registry for the current provider name, version and supported resources before you commit to one. Provider coverage differs a lot between products.
Resources are the things you manage. Data sources read existing things without managing them. Variables parameterise a configuration, outputs expose values, and modules package reusable groups of resources.
State is a file in which Terraform records what it created and the real IDs. Terraform uses it to know that aws_vpc.main in your code is vpc-0abc... in the cloud. State matters for three reasons:
- Losing it means Terraform no longer knows what it owns.
- It can contain secrets in plain text, including passwords passed to providers.
- Two people applying at the same time can corrupt it.
So do not keep state on a laptop for shared work. Use a remote backend with locking, such as an S3 bucket with a lock mechanism, Azure Storage, or a Terraform Cloud or HCP Terraform workspace, and restrict who can read it.
Warning
Never commit terraform.tfstate or .tfvars files that hold credentials to Git. Add them to .gitignore. Pass secrets through environment variables (TF_VAR_name), a secrets manager, or the provider's own environment variables.
The workflow¶
terraform init # download providers, configure the backend
terraform fmt # format the files
terraform validate # check syntax and internal consistency
terraform plan # show what would change, change nothing
terraform apply # apply the plan, asks for confirmation
terraform destroy # remove everything in this configuration
plan is the central habit. It shows each resource marked as create (+), update in place (~), or destroy (-), including destroy-and-recreate (-/+). Read it every time. In a pipeline, save the plan and apply exactly that plan so nothing changes between review and apply:
Example: a small VPC with two subnets¶
This uses AWS because the resources are stable and anyone can try it in a free-tier-sized account. The same pattern applies to any provider. Check the AWS provider documentation for the current version constraint.
main.tf:
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
variable "region" {
type = string
default = "eu-west-1"
}
variable "subnets" {
type = map(object({
cidr = string
az = string
}))
default = {
app = { cidr = "10.20.1.0/24", az = "eu-west-1a" }
db = { cidr = "10.20.2.0/24", az = "eu-west-1b" }
}
}
resource "aws_vpc" "main" {
cidr_block = "10.20.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = { Name = "lab-vpc" }
}
resource "aws_subnet" "this" {
for_each = var.subnets
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr
availability_zone = each.value.az
tags = { Name = "lab-${each.key}" }
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
}
resource "aws_route_table_association" "app" {
subnet_id = aws_subnet.this["app"].id
route_table_id = aws_route_table.public.id
}
output "vpc_id" {
value = aws_vpc.main.id
}
Notice that nothing says "create the VPC first". aws_subnet.this refers to aws_vpc.main.id, so Terraform builds a dependency graph and orders the work itself. for_each over a map gives you one subnet per entry, and adding a third entry to the map adds a third subnet on the next apply without touching the others.
Run it:
Now change the db subnet CIDR and plan again. Terraform will show the subnet as -/+ (replace), because AWS cannot change a subnet's CIDR in place. That is the kind of detail the plan exposes before you break something.
Example: ACI in a few lines¶
The ACI provider follows the same shape, with the APIC as the target:
terraform {
required_providers {
aci = {
source = "CiscoDevNet/aci"
}
}
}
provider "aci" {
username = var.apic_user
password = var.apic_password
url = "https://apic.example.net"
insecure = true # lab only
}
resource "aci_tenant" "demo" {
name = "demo"
}
resource "aci_vrf" "prod" {
tenant_dn = aci_tenant.demo.id
name = "prod"
}
Resource names and required arguments change between provider versions, so treat this as a shape and read the provider documentation for the version you pin.
Drift, import and destroy¶
- Drift is when someone changes a resource by hand.
terraform planshows it as a difference, and the nextapplyputs it back. That is the same drift-correction idea as a NAPALM replace, applied at the API level. - Import brings an existing resource under Terraform's control. Write the resource block, then run
terraform import <address> <id>. Recent versions also supportimportblocks in configuration. Plan afterwards and keep editing the code until the plan shows no changes. - Destroy deletes everything in the state. Protect important resources with
lifecycle { prevent_destroy = true }and restrict who can run it in production.
Where Terraform fits and where it does not¶
Terraform suits:
- Cloud network foundations, built once and changed rarely.
- Controllers with solid providers (ACI, Meraki, SD-WAN managers) where the API is the interface.
- Environments you create and tear down, such as labs and per-project networks.
It is a weaker fit for:
- Brownfield estates of CLI-only switches and routers, where Ansible or NAPALM is more natural.
- Anything where resource coverage in the provider is thin. If the provider lacks the resource you need, you cannot manage it declaratively.
- Teams that will not guard the state file. Unprotected state is a security and reliability problem.
OpenTofu is an open-source fork of Terraform that reads the same configuration language and works with most of the same providers. Terraform itself is distributed under the HashiCorp Business Source License, so read the licence terms if you embed it in a product. For internal use of either tool, the practical differences are small.
Run terraform plan from a CI pipeline on every pull request and apply only from the main branch after approval, as described in Git and CI/CD for network configs.
References¶
- Terraform documentation
- Terraform Registry
- AWS provider documentation
- Cisco ACI provider
- Cisco IOS XE provider
- OpenTofu
Unlock the full question banks and cram sheets
A free account opens every question bank, the timed practice exams, the printable cram sheets, all the simulations and the lab files. Without one you see a 5-question sample of each.
No card needed. Study tips by email are optional and off unless you tick the box. Your address is never sold or shared. Privacy policy ยท Terms
Signed in as . Your account
You're on the free account, so lessons after the first module, question banks, exams and cram sheets show a sample. See Track Pass and All-Access
Also from NetManager Systems: NetManager, network monitoring for multi-vendor networks. Try the NetManager, NetDiag, PCAP Analyzer and OpsDash demos: every new account gets a free 7-day read-only login to each.