Lesson 1 of 6

Terraform Fundamentals

Objective

In this lesson you will learn the Terraform workflow for configuring Cisco IOS XE devices: init, plan, apply, and destroy. You will configure a single VLAN on an IOS XE device using the IOS XE Terraform provider (RESTCONF). This matters in production because Infrastructure as Code (IaC) gives repeatable, auditable, and reversible changes across many devices — for example, creating VLANs consistently across an entire campus or data-center leaf layer. In a real-world scenario, teams use Terraform to ensure every switch has the same VLANs and names before deploying new services or onboarding tenants.

Topology & Device Table

ASCII topology (management-plane view). The Terraform host uses RESTCONF (HTTPS) to the IOS XE device on port 443.

Workstation: 192.0.2.10 IOS XE Device (mgmt interface Gi0/0): 192.0.2.1 (RESTCONF on 443)

Network Topology Diagram

Device Table

DeviceInterfaceIP AddressSubnet MaskRole
Terraform workstationeth0192.0.2.10255.255.255.0Runs terraform CLI (client)
IOS XE Device (lab-router.lab.nhprep.com)GigabitEthernet0/0192.0.2.1255.255.255.0Target device running RESTCONF

Note: The IOS XE Terraform provider uses RESTCONF with JSON encoding to configure the device. The provider block references the device via a URL (see steps below).

Key Concepts (Theory + Practical)

  • Terraform Lifecycle (init, plan, apply, destroy)

    • init downloads provider plugins and prepares the working directory; plan computes a proposed execution plan; apply makes the changes to reach the desired state; destroy removes resources created by Terraform.
    • In production, plan is essential for peer review because it shows proposed changes before they happen.
  • Provider and Resources

    • The provider is the plugin that knows how to talk to a specific platform (here: iosxe). The resource is a declarative representation of a device feature (e.g., a VLAN).
    • The provider for IOS XE communicates via RESTCONF (HTTPS/JSON) to the device.
  • Stateful behavior

    • Terraform is stateful: it keeps a state file to know what it previously configured. This allows Terraform to compute diffs and to remove the configuration cleanly with terraform destroy.
    • In production this prevents drift and allows safe rollbacks.
  • RESTCONF vs NETCONF vs gNMI

    • RESTCONF uses HTTPS and JSON (RFC7951 JSON_IETF). NETCONF uses SSH and XML. gNMI uses HTTP/2 and protobufs. Terraform's IOS XE provider uses RESTCONF with JSON for payloads and operations (GET, POST, PUT, PATCH, DELETE).
  • Verification & idempotency

    • Terraform resources are idempotent: applying the same config twice will not create duplicates. You verify the resource exists either by querying the device (via the provider or device CLI translated to RESTCONF) or by inspecting Terraform state.

Step-by-step configuration

Step 1: Create Terraform execution plan file (main.tf)

What we are doing: Create the Terraform configuration file that declares the IOS XE provider and a VLAN resource (VLAN 511). This file is the declarative source of truth that Terraform will use.

# main.tf
terraform {
  required_providers {
    iosxe = {
      source  = "CiscoDevNet/iosxe"
      version = ">=0.5.0"
    }
  }
}

provider "iosxe" {
  username = var.device_username
  password = var.device_password
  url      = var.host_url
}

resource "iosxe_vlan" "example" {
  vlan_id  = 511
  name     = "Vlan511"
  shutdown = false
}

# Optional data source to read the VLAN after creation
data "iosxe_vlan" "example_read" {
  vlan_id = 511
}

What just happened: The terraform block tells Terraform to use the iosxe provider from the registry. The provider "iosxe" block declares credentials and the device URL as variables (secure practice: do not hard-code credentials). The resource "iosxe_vlan" "example" tells Terraform we want VLAN 511 present, named "Vlan511", and administratively up (shutdown = false). The data block shows how you can read back the VLAN after Terraform applies it.

Real-world note: Storing provider credentials as variables and using a secrets backend (not shown here) is recommended in production.

Verify:

# Show the main.tf file contents to verify it was saved
cat main.tf
# Expected output (complete file contents):
terraform {
  required_providers {
    iosxe = {
      source  = "CiscoDevNet/iosxe"
      version = ">=0.5.0"
    }
  }
}

provider "iosxe" {
  username = var.device_username
  password = var.device_password
  url      = var.host_url
}

resource "iosxe_vlan" "example" {
  vlan_id  = 511
  name     = "Vlan511"
  shutdown = false
}

data "iosxe_vlan" "example_read" {
  vlan_id = 511
}


<div class="topology-diagram">
<img src="data:image/svg+xml;base64,PD9wbGFudHVtbCAxLjIwMjYuMT8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyIgZGF0YS1kaWFncmFtLXR5cGU9Ik5XRElBRyIgaGVpZ2h0PSIxNjZweCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgc3R5bGU9IndpZHRoOjM3OXB4O2hlaWdodDoxNjZweDtiYWNrZ3JvdW5kOiNGRkZGRkY7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNzkgMTY2IiB3aWR0aD0iMzc5cHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzLz48Zz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3OS4wNDg4IiB4PSI1IiB5PSIxNi4xMzg3Ij5NYW5hZ2VtZW50PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijc2LjU2NDUiIHg9IjcuNDg0NCIgeT0iMzAuMTA3NCI+MTkyLjAuMi4wLzI0PC90ZXh0PjxyZWN0IGZpbGw9IiNFMkUyRjAiIGhlaWdodD0iNSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIgd2lkdGg9IjI4Mi4xNTYzIiB4PSI4OS4wNDg4IiB5PSIxNi40Njg4Ii8+PHBhdGggZD0iTTE2Mi43NzE1LDIxLjQ2ODggTDE2Mi43NzE1LDcwLjY3NTgiIGZpbGw9Im5vbmUiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MTsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI1OS40Nzk1IiB4PSIxMjQuOTQwMiIgeT0iMzQuNTc1NyI+MTkyLjAuMi4xMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIyNS4wNTA4IiB4PSIxMjQuOTQwMiIgeT0iNDcuMzgwNCI+ZXRoMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3NS42NjI2IiB4PSIxMjQuOTQwMiIgeT0iNjAuMTg1MSI+VGVycmFmb3JtIENMSTwvdGV4dD48cGF0aCBkPSJNMzAzLjg0OTYsMjEuNDY4OCBMMzAzLjg0OTYsNzAuNjc1OCIgZmlsbD0ibm9uZSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjU5LjQ3OTUiIHg9IjI0OS42MzkyIiB5PSIzNC41NzU3Ij4xOTIuMC4yLjExPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijk0LjIzMDUiIHg9IjI0OS42MzkyIiB5PSI0Ny4zODA0Ij5HaWdhYml0RXRoZXJuZXQwPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEwOC40MjA5IiB4PSIyNDkuNjM5MiIgeT0iNjAuMTg1MSI+UkVTVENPTkYgVENQLzQ0MzwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjExMy40NDUzIiB4PSIxMDQuMDQ4OCIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjkzLjQ0NTMiIHg9IjExNC4wNDg4IiB5PSI5MS44MTQ1Ij5UZXJyYWZvcm1fSG9zdDwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjEwOC43MTA5IiB4PSIyNDcuNDk0MSIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijg4LjcxMDkiIHg9IjI1Ny40OTQxIiB5PSI5MS44MTQ1Ij5JT1NfWEVfUm91dGVyPC90ZXh0Pjw/cGxhbnR1bWwtc3JjIG9vakZvS25DTHdaY0tiMzhJb3FmcG9fQUxsMURwNGpDSnlyRHBJaTEyb2llOUFRYTVBS001b2xPQVlXUE1YaGY2VVcxaVJ3SFlIOU9DWUlyZzJXbkJSeWVEenVabHhYNDhIZ0JPYUUzNkEzUDZLMEloYloxc1NUSkFUT1FSOEFkVnQxeVg2anlLN3ZmSU1lSFRXQ0NPX0JTQ3pDSmFwOUJONGlvS2VrMGZXNEQydDhEM2Q3c3p0REoyNzRFcTNTbkNPUU9MeWpMMm0wMD8+PC9nPjwvc3ZnPg==" alt="Network Topology Diagram" style="max-width:100%;height:auto;background:#fff;padding:16px;border:1px solid #e5e7eb;border-radius:8px;" />
</div>

cisco
terraform init

What just happened: terraform init contacts the Terraform Registry, downloads the iosxe provider plugin (CiscoDevNet/iosxe) into the local working directory, and initializes the backend. This ensures Terraform knows how to translate iosxe resources into RESTCONF calls.

Real-world note: In CI/CD pipelines, terraform init is part of the job that prepares providers and modules before terraform plan.

Verify:

terraform init
# Expected output snippet (complete lines show provider initialization):
Initializing the backend...

Initializing provider plugins...
- Finding CiscoDevNet/iosxe versions matching ">=0.5.0"...
- Installing CiscoDevNet/iosxe v0.5.x...
Terraform has been successfully initialized!


<div class="topology-diagram">
<img src="data:image/svg+xml;base64,PD9wbGFudHVtbCAxLjIwMjYuMT8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyIgZGF0YS1kaWFncmFtLXR5cGU9Ik5XRElBRyIgaGVpZ2h0PSIxNjZweCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgc3R5bGU9IndpZHRoOjM3OXB4O2hlaWdodDoxNjZweDtiYWNrZ3JvdW5kOiNGRkZGRkY7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNzkgMTY2IiB3aWR0aD0iMzc5cHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzLz48Zz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3OS4wNDg4IiB4PSI1IiB5PSIxNi4xMzg3Ij5NYW5hZ2VtZW50PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijc2LjU2NDUiIHg9IjcuNDg0NCIgeT0iMzAuMTA3NCI+MTkyLjAuMi4wLzI0PC90ZXh0PjxyZWN0IGZpbGw9IiNFMkUyRjAiIGhlaWdodD0iNSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIgd2lkdGg9IjI4Mi4xNTYzIiB4PSI4OS4wNDg4IiB5PSIxNi40Njg4Ii8+PHBhdGggZD0iTTE2Mi43NzE1LDIxLjQ2ODggTDE2Mi43NzE1LDcwLjY3NTgiIGZpbGw9Im5vbmUiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MTsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI1OS40Nzk1IiB4PSIxMjQuOTQwMiIgeT0iMzQuNTc1NyI+MTkyLjAuMi4xMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIyNS4wNTA4IiB4PSIxMjQuOTQwMiIgeT0iNDcuMzgwNCI+ZXRoMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3NS42NjI2IiB4PSIxMjQuOTQwMiIgeT0iNjAuMTg1MSI+VGVycmFmb3JtIENMSTwvdGV4dD48cGF0aCBkPSJNMzAzLjg0OTYsMjEuNDY4OCBMMzAzLjg0OTYsNzAuNjc1OCIgZmlsbD0ibm9uZSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjU5LjQ3OTUiIHg9IjI0OS42MzkyIiB5PSIzNC41NzU3Ij4xOTIuMC4yLjExPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijk0LjIzMDUiIHg9IjI0OS42MzkyIiB5PSI0Ny4zODA0Ij5HaWdhYml0RXRoZXJuZXQwPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEwOC40MjA5IiB4PSIyNDkuNjM5MiIgeT0iNjAuMTg1MSI+UkVTVENPTkYgVENQLzQ0MzwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjExMy40NDUzIiB4PSIxMDQuMDQ4OCIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjkzLjQ0NTMiIHg9IjExNC4wNDg4IiB5PSI5MS44MTQ1Ij5UZXJyYWZvcm1fSG9zdDwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjEwOC43MTA5IiB4PSIyNDcuNDk0MSIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijg4LjcxMDkiIHg9IjI1Ny40OTQxIiB5PSI5MS44MTQ1Ij5JT1NfWEVfUm91dGVyPC90ZXh0Pjw/cGxhbnR1bWwtc3JjIG9vakZvS25DTHdaY0tiMzhJb3FmcG9fQUxsMURwNGpDSnlyRHBJaTEyb2llOUFRYTVBS001b2xPQVlXUE1YaGY2VVcxaVJ3SFlIOU9DWUlyZzJXbkJSeWVEenVabHhYNDhIZ0JPYUUzNkEzUDZLMEloYloxc1NUSkFUT1FSOEFkVnQxeVg2anlLN3ZmSU1lSFRXQ0NPX0JTQ3pDSmFwOUJONGlvS2VrMGZXNEQydDhEM2Q3c3p0REoyNzRFcTNTbkNPUU9MeWpMMm0wMD8+PC9nPjwvc3ZnPg==" alt="Network Topology Diagram" style="max-width:100%;height:auto;background:#fff;padding:16px;border:1px solid #e5e7eb;border-radius:8px;" />
</div>

cisco
terraform plan -var 'device_username=admin' -var 'device_password=Lab@123' -var 'host_url=https://lab-router.lab.nhprep.com:443'

What just happened: Terraform authenticated to the provider (iosxe) and used RESTCONF to query the device state (stateful provider behavior). The plan shows Terraform wants to create iosxe_vlan.example with vlan_id 511 and name "Vlan511". No changes are made yet.

Real-world note: Always run terraform plan and have another engineer review it before apply in production environments.

Verify:

terraform plan -var 'device_username=admin' -var 'device_password=Lab@123' -var 'host_url=https://lab-router.lab.nhprep.com:443'
# Expected plan output (complete):
Refreshing Terraform state in-memory prior to plan...
...
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # iosxe_vlan.example will be created
  + resource "iosxe_vlan" "example" {
      + id       = (known after apply)
      + name     = "Vlan511"
      + shutdown = false
      + vlan_id  = 511
    }

Plan: 1 to add, 0 to change, 0 to destroy.


<div class="topology-diagram">
<img src="data:image/svg+xml;base64,PD9wbGFudHVtbCAxLjIwMjYuMT8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyIgZGF0YS1kaWFncmFtLXR5cGU9Ik5XRElBRyIgaGVpZ2h0PSIxNjZweCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgc3R5bGU9IndpZHRoOjM3OXB4O2hlaWdodDoxNjZweDtiYWNrZ3JvdW5kOiNGRkZGRkY7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNzkgMTY2IiB3aWR0aD0iMzc5cHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzLz48Zz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3OS4wNDg4IiB4PSI1IiB5PSIxNi4xMzg3Ij5NYW5hZ2VtZW50PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijc2LjU2NDUiIHg9IjcuNDg0NCIgeT0iMzAuMTA3NCI+MTkyLjAuMi4wLzI0PC90ZXh0PjxyZWN0IGZpbGw9IiNFMkUyRjAiIGhlaWdodD0iNSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIgd2lkdGg9IjI4Mi4xNTYzIiB4PSI4OS4wNDg4IiB5PSIxNi40Njg4Ii8+PHBhdGggZD0iTTE2Mi43NzE1LDIxLjQ2ODggTDE2Mi43NzE1LDcwLjY3NTgiIGZpbGw9Im5vbmUiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MTsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI1OS40Nzk1IiB4PSIxMjQuOTQwMiIgeT0iMzQuNTc1NyI+MTkyLjAuMi4xMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIyNS4wNTA4IiB4PSIxMjQuOTQwMiIgeT0iNDcuMzgwNCI+ZXRoMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3NS42NjI2IiB4PSIxMjQuOTQwMiIgeT0iNjAuMTg1MSI+VGVycmFmb3JtIENMSTwvdGV4dD48cGF0aCBkPSJNMzAzLjg0OTYsMjEuNDY4OCBMMzAzLjg0OTYsNzAuNjc1OCIgZmlsbD0ibm9uZSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjU5LjQ3OTUiIHg9IjI0OS42MzkyIiB5PSIzNC41NzU3Ij4xOTIuMC4yLjExPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijk0LjIzMDUiIHg9IjI0OS42MzkyIiB5PSI0Ny4zODA0Ij5HaWdhYml0RXRoZXJuZXQwPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEwOC40MjA5IiB4PSIyNDkuNjM5MiIgeT0iNjAuMTg1MSI+UkVTVENPTkYgVENQLzQ0MzwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjExMy40NDUzIiB4PSIxMDQuMDQ4OCIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjkzLjQ0NTMiIHg9IjExNC4wNDg4IiB5PSI5MS44MTQ1Ij5UZXJyYWZvcm1fSG9zdDwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjEwOC43MTA5IiB4PSIyNDcuNDk0MSIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijg4LjcxMDkiIHg9IjI1Ny40OTQxIiB5PSI5MS44MTQ1Ij5JT1NfWEVfUm91dGVyPC90ZXh0Pjw/cGxhbnR1bWwtc3JjIG9vakZvS25DTHdaY0tiMzhJb3FmcG9fQUxsMURwNGpDSnlyRHBJaTEyb2llOUFRYTVBS001b2xPQVlXUE1YaGY2VVcxaVJ3SFlIOU9DWUlyZzJXbkJSeWVEenVabHhYNDhIZ0JPYUUzNkEzUDZLMEloYloxc1NUSkFUT1FSOEFkVnQxeVg2anlLN3ZmSU1lSFRXQ0NPX0JTQ3pDSmFwOUJONGlvS2VrMGZXNEQydDhEM2Q3c3p0REoyNzRFcTNTbkNPUU9MeWpMMm0wMD8+PC9nPjwvc3ZnPg==" alt="Network Topology Diagram" style="max-width:100%;height:auto;background:#fff;padding:16px;border:1px solid #e5e7eb;border-radius:8px;" />
</div>

cisco
terraform apply -auto-approve -var 'device_username=admin' -var 'device_password=Lab@123' -var 'host_url=https://lab-router.lab.nhprep.com:443'

What just happened: Terraform sent RESTCONF API requests (JSON payloads) to lab-router.lab.nhprep.com:443 to create VLAN 511 with the specified attributes. The provider recorded the created resource in Terraform state so future plans are based on what Terraform believes exists.

Real-world note: Because Terraform is stateful, if someone manually changes the VLAN on the device, Terraform may detect drift on the next plan. Use automation practices (change control) to avoid manual drift.

Verify:

terraform apply -auto-approve -var 'device_username=admin' -var 'device_password=Lab@123' -var 'host_url=https://lab-router.lab.nhprep.com:443'
# Expected output (complete lines):
iosxe_vlan.example: Creating...
iosxe_vlan.example: Creation complete after 2s [id=511]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

To inspect the resource recorded in state:

terraform state show iosxe_vlan.example
# Expected output:
# iosxe_vlan.example:
resource "iosxe_vlan" "example" {
    id       = "511"
    name     = "Vlan511"
    shutdown = "false"
    vlan_id  = "511"
    provider = "provider[\"registry.terraform.io/CiscoDevNet/iosxe\"]"
}


<div class="topology-diagram">
<img src="data:image/svg+xml;base64,PD9wbGFudHVtbCAxLjIwMjYuMT8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyIgZGF0YS1kaWFncmFtLXR5cGU9Ik5XRElBRyIgaGVpZ2h0PSIxNjZweCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSIgc3R5bGU9IndpZHRoOjM3OXB4O2hlaWdodDoxNjZweDtiYWNrZ3JvdW5kOiNGRkZGRkY7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzNzkgMTY2IiB3aWR0aD0iMzc5cHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzLz48Zz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3OS4wNDg4IiB4PSI1IiB5PSIxNi4xMzg3Ij5NYW5hZ2VtZW50PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijc2LjU2NDUiIHg9IjcuNDg0NCIgeT0iMzAuMTA3NCI+MTkyLjAuMi4wLzI0PC90ZXh0PjxyZWN0IGZpbGw9IiNFMkUyRjAiIGhlaWdodD0iNSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIgd2lkdGg9IjI4Mi4xNTYzIiB4PSI4OS4wNDg4IiB5PSIxNi40Njg4Ii8+PHBhdGggZD0iTTE2Mi43NzE1LDIxLjQ2ODggTDE2Mi43NzE1LDcwLjY3NTgiIGZpbGw9Im5vbmUiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MTsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI1OS40Nzk1IiB4PSIxMjQuOTQwMiIgeT0iMzQuNTc1NyI+MTkyLjAuMi4xMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIyNS4wNTA4IiB4PSIxMjQuOTQwMiIgeT0iNDcuMzgwNCI+ZXRoMDwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3NS42NjI2IiB4PSIxMjQuOTQwMiIgeT0iNjAuMTg1MSI+VGVycmFmb3JtIENMSTwvdGV4dD48cGF0aCBkPSJNMzAzLjg0OTYsMjEuNDY4OCBMMzAzLjg0OTYsNzAuNjc1OCIgZmlsbD0ibm9uZSIgc3R5bGU9InN0cm9rZTojMTgxODE4O3N0cm9rZS13aWR0aDoxOyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjU5LjQ3OTUiIHg9IjI0OS42MzkyIiB5PSIzNC41NzU3Ij4xOTIuMC4yLjExPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijk0LjIzMDUiIHg9IjI0OS42MzkyIiB5PSI0Ny4zODA0Ij5HaWdhYml0RXRoZXJuZXQwPC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEwOC40MjA5IiB4PSIyNDkuNjM5MiIgeT0iNjAuMTg1MSI+UkVTVENPTkYgVENQLzQ0MzwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjExMy40NDUzIiB4PSIxMDQuMDQ4OCIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjkzLjQ0NTMiIHg9IjExNC4wNDg4IiB5PSI5MS44MTQ1Ij5UZXJyYWZvcm1fSG9zdDwvdGV4dD48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjMzLjk2ODgiIHN0eWxlPSJzdHJva2U6IzE4MTgxODtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjEwOC43MTA5IiB4PSIyNDcuNDk0MSIgeT0iNzAuNjc1OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijg4LjcxMDkiIHg9IjI1Ny40OTQxIiB5PSI5MS44MTQ1Ij5JT1NfWEVfUm91dGVyPC90ZXh0Pjw/cGxhbnR1bWwtc3JjIG9vakZvS25DTHdaY0tiMzhJb3FmcG9fQUxsMURwNGpDSnlyRHBJaTEyb2llOUFRYTVBS001b2xPQVlXUE1YaGY2VVcxaVJ3SFlIOU9DWUlyZzJXbkJSeWVEenVabHhYNDhIZ0JPYUUzNkEzUDZLMEloYloxc1NUSkFUT1FSOEFkVnQxeVg2anlLN3ZmSU1lSFRXQ0NPX0JTQ3pDSmFwOUJONGlvS2VrMGZXNEQydDhEM2Q3c3p0REoyNzRFcTNTbkNPUU9MeWpMMm0wMD8+PC9nPjwvc3ZnPg==" alt="Network Topology Diagram" style="max-width:100%;height:auto;background:#fff;padding:16px;border:1px solid #e5e7eb;border-radius:8px;" />
</div>

cisco
terraform destroy -auto-approve -var 'device_username=admin' -var 'device_password=Lab@123' -var 'host_url=https://lab-router.lab.nhprep.com:443'

What just happened: Terraform used the provider to send a DELETE (or equivalent RESTCONF) request to the device to remove VLAN 511. The state file is updated to reflect that the resource no longer exists.

Real-world note: destroy is powerful and should be restricted in production. Use role-based access control and policies to prevent accidental destruction.

Verify:

terraform destroy -auto-approve -var 'device_username=admin' -var 'device_password=Lab@123' -var 'host_url=https://lab-router.lab.nhprep.com:443'
# Expected output:
iosxe_vlan.example: Destroying...
iosxe_vlan.example: Destruction complete after 1s

Destroy complete! Resources: 1 destroyed.

Optional device-side verification using IOS XE CLI formatting for RESTCONF/NETCONF view:

# On the IOS XE device, view AAA configuration in RESTCONF JSON format (example of format usage)
show running-config aaa | format restconf -json
# Expected output: (device outputs AAA config in JSON_IETF encoding)
{
  "aaa" : {
    ... (JSON representation of AAA config) ...
  }
}

Tip: The show ... | format restconf -json command demonstrates how IOS XE can format configuration in RESTCONF JSON — useful to validate payloads or translate CLI configs to model-driven formats.

Verification Checklist

  • Check 1: Terraform initialized successfully — verify by terraform init output showing iosxe provider installed.
  • Check 2: Terraform plan shows + create for iosxe_vlan.example — verify with terraform plan output.
  • Check 3: After apply, Terraform state shows vlan_id = 511 and name = "Vlan511" — verify with terraform state show iosxe_vlan.example.
  • Check 4: After destroy, plan shows no resources or 0 to add — verify with terraform plan or check terraform state list is empty.

Common Mistakes

SymptomCauseFix
terraform init fails to download providerNo internet or Terraform Registry blockedEnsure workstation can reach registry; use an approved mirror or private registry
terraform plan shows authentication failureIncorrect username/password or host_urlVerify variables; use correct -var values or env vars; confirm RESTCONF reachable at URL and port 443
After apply, resource not present on deviceProvider could not communicate or device rejected RESTCONF payloadCheck RESTCONF enabled on device, correct URL, and that account has sufficient privileges; check TLS and certificate trust
terraform apply succeeds but future plan wants to recreate the resourceState file was not preserved or manual device changes created driftEnsure state file is stored and protected; discourage manual changes or import existing resources into state

Key Takeaways

  • Terraform's workflow is: init (prepare providers), plan (preview), apply (enact), destroy (remove). Use plan for safety and peer reviews in production.
  • The IOS XE Terraform provider uses RESTCONF (HTTPS/JSON). Provider configuration includes username, password, and url.
  • Terraform is stateful — it stores knowledge of created resources so it can compute diffs and support safe destroy operations.
  • Use variables (never hard-code credentials) and consider a secrets manager for production. Keep terraform plan outputs as part of change control records.

Final real-world insight: In large environments, teams use Terraform to deploy consistent VLAN, ACL, and interface state across hundreds of devices. The pattern shown here (provider + resource + plan/apply/destroy) is the foundation for scaling network configuration safely and audibly.