Azure Network Infrastructure with Terraform
Deploy a secured Azure VNet, subnet, and NSG using Terraform IaC.
Introduction
30 Second Summary
Your new job description says "Terraform" and "Azure networking" and you start on Monday. You need to go from zero to confidently deploying cloud infrastructure before your first standup.
In this project, you will write Terraform configuration files that deploy a secured Azure virtual network, complete with subnets and firewall rules. You will run the full Terraform workflow to create real cloud resources, see them live in the Azure Portal, and tear them all down with a single command.
What You'll Build
You will type terraform apply in your terminal, watch real Azure infrastructure spin up in seconds, then open the Azure Portal and see your virtual network, subnet, and security rules live.
By the end of this project, you'll have:
- A live Azure virtual network with firewall rules, deployed entirely from code you wrote and can re-run at any time.
- Hands-on experience with the complete Terraform workflow (init, plan, apply, destroy) that you will use daily in your new role.
- Production-style configuration using variables and outputs that mirrors how your team structures real infrastructure projects.
- Secret Mission: Refactor your infrastructure into a reusable Terraform module that teammates could call from their own projects.
Are there any prerequisites?
You need a Windows machine with VS Code and Git already installed. You also need an Azure account (the free tier works perfectly for this project since all resources we create are free). No prior Terraform experience is required.
Before We Start
Before diving into any tools or code, let's lock in exactly what you're about to build and why it matters for your first week on the job.
Install Terraform and Azure CLI
You cannot deploy infrastructure as code without the right tools on your machine. Right now you have VS Code and an Azure account, but nothing that can translate configuration files into real cloud resources.
In this step, you will install two command-line tools: Terraform (which reads your configuration and communicates with cloud providers) and the Azure CLI (which authenticates you to your Azure subscription).
In this step, get ready to:
- Install Terraform CLI on Windows.
- Install Azure CLI using WinGet.
- Authenticate to your Azure subscription.
Install Terraform CLI
Terraform is distributed as a single binary file. On Windows, you download it, place it in a dedicated folder, and tell Windows where to find it by adding that folder to your system PATH.
- Press the Windows key to open your search bar.
- Type PowerShell and press Enter to open it.
What is Terraform?
Terraform is an Infrastructure as Code tool made by HashiCorp. You write configuration files that describe what cloud resources you want, and Terraform creates, updates, or deletes those resources for you.
Instead of clicking through a web console to create resources manually, you define everything in code. This makes your infrastructure repeatable, version-controlled, and shareable with your team.
- Open your browser and go to developer.hashicorp.com/terraform/install.
- Scroll down to the Windows section.
- Click the AMD64 link under Binary download to download the zip file.
The zip file downloads to your Downloads folder. Now you will extract it and place it where Windows can find it.
- Create the C:\terraform folder and extract the binary into it by running these commands:
New-Item -ItemType Directory -Force -Path "C:\terraform"
Expand-Archive -Path "$env:USERPROFILE\Downloads\terraform_*_windows_amd64.zip" -DestinationPath "C:\terraform" -Force
What do these commands do?
New-Item creates the C:\terraform folder if it does not already exist. Expand-Archive unzips the downloaded file into that folder.
The wildcard * in the filename matches whatever version number is in the zip. This means the command works regardless of the exact version you downloaded.
- Confirm the binary is in place by running:
dir C:\terraform
You should see terraform.exe listed in the output.
Don't see terraform.exe?
Check your Downloads folder for the actual zip filename. If you have multiple zip files or the wildcard did not match, replace the path in the Expand-Archive command with the exact filename you downloaded.
help me find and extract the Terraform zip file I downloaded
- Add C:\terraform to your user PATH so Windows can find the binary from anywhere by running:
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\terraform", "User")
What does this command do?
The PATH is a list of folders that Windows searches when you type a command. By adding C:\terraform to it, you tell Windows to look there whenever you type terraform.
The change is permanent for your user account, but your current PowerShell session still has the old PATH loaded. You need to close and reopen PowerShell for the change to take effect.
- Close your PowerShell window completely.
- Open a new PowerShell window (press the Windows key, type PowerShell, press Enter).
Before you run the version check, what do you think the output will show if the PATH was set correctly?
- Verify Terraform is installed by running:
terraform --version
✔️ I see version 1.2.0 or higher
Terraform is installed and on your PATH. You should see output starting with Terraform v1.15 (or similar). You are ready to move on.
ⓧ I see an older version
You have a previous Terraform installation on your machine. This project requires version 1.2.0 or higher.
- Go back to developer.hashicorp.com/terraform/install and download the latest Windows AMD64 binary.
- Extract it to C:\terraform again, overwriting the old file.
- Close and reopen PowerShell, then run terraform --version again.
If the old version persists, you may have another terraform.exe earlier on your PATH. Run where.exe terraform to find it and remove or rename the older one.
ⓧ Command not found
PowerShell cannot find the terraform command. This means the PATH was not updated correctly.
- Confirm the binary exists by running dir C:\terraform\terraform.exe. If it does not exist, re-run the extract step above.
- Check your PATH by running $env:Path -split ";" and look for C:\terraform in the list.
- If C:\terraform is not listed, re-run the SetEnvironmentVariable command, then close and reopen PowerShell.
help me fix my Terraform PATH on Windows
Install Azure CLI
The Azure CLI is the command-line tool for managing Azure resources. Terraform uses it to authenticate to your subscription so it can create resources on your behalf.
- Install the Azure CLI using WinGet by running this command:
winget install --exact --id Microsoft.AzureCLI
What does this command do?
WinGet is the built-in Windows package manager. The --exact flag ensures you get the official Microsoft Azure CLI package and not a similarly-named alternative. The --id flag specifies the exact package identifier.
WinGet may show prompts
WinGet might ask you to accept source agreements the first time you use it. Type Y and press Enter to continue. The install may take a minute or two.
You must reopen PowerShell after this install
The Azure CLI will not be recognized until you start a fresh terminal session. The installer adds az to your PATH, but your current session still has the old PATH loaded.
- Close your PowerShell window completely and open a new one (press the Windows key, type PowerShell, press Enter).
- Verify the Azure CLI is installed by running:
az --version
✔️ I see a version number
The Azure CLI is ready. You should see azure-cli followed by a version number (e.g. 2.89.0). Move on to the next substep.
ⓧ Command not found
PowerShell cannot find the az command. The most common cause is not reopening the terminal after installation.
- Close PowerShell and open a brand new window.
- Run az --version again.
- If it still fails, try running the install command again: winget install --exact --id Microsoft.AzureCLI.
help me troubleshoot my Azure CLI installation
Authenticate to Azure
Now that both tools are installed, you need to connect the Azure CLI to your Azure account. Terraform uses this connection to create resources in your subscription.
- Sign in to Azure by running:
az login
What happens when you run az login?
Your default browser opens to the Microsoft sign-in page. Sign in with the same credentials you use for the Azure Portal.
After you authenticate in the browser, PowerShell displays your subscription details as JSON. If the browser does not open automatically, copy the URL shown in the terminal and paste it into your browser manually.
- Sign in with your Azure account credentials in the browser window that opens.
- Return to PowerShell after signing in.
What do you expect to see if your login succeeded? Think about what information Azure would show you about your account.
- Confirm your active subscription by running:
az account show
You should see JSON output containing your subscription name, subscription ID, and tenant ID. This confirms Terraform can reach your Azure account.
Seeing a subscription error?
If you have multiple Azure subscriptions and the wrong one is active, set the correct one by running:
az account set --subscription "your-subscription-id"
Replace your-subscription-id with the subscription ID shown in the Azure Portal under Subscriptions.
help me fix my Azure subscription
Your machine is ready. Both tools are installed, and you are authenticated to Azure. Next up, you will write your first Terraform configuration and deploy a real resource to the cloud.
Deploy Your First Resource
Your tools are installed and you are authenticated to Azure. But right now, nothing exists in your subscription that you created.
You need to see Terraform actually create something real before building anything complex. A resource group is the simplest Azure resource and proves the entire pipeline works end-to-end.
In this step, get ready to:
- Write a Terraform configuration file that defines your Azure provider and a resource group.
- Run the Terraform workflow (init, plan, apply) to deploy real infrastructure.
- Verify the resource group exists in the Azure Portal.
Create your project directory and configuration file
Every Terraform project lives in its own folder. Terraform reads all .tf files in the current directory and combines them into a single configuration.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir azure-terraform-lab
cd azure-terraform-lab
- Confirm the folder was created by running:
ls
You should see an empty directory listing. That is expected.
- Open this folder in VS Code by running:
code .
VS Code opens with the azure-terraform-lab folder loaded in the Explorer sidebar.
VS Code did not open?
If you see an error that code is not recognized, open VS Code manually. Press the Windows key, type VS Code, and press Enter. Then click File, select Open Folder, and navigate to Desktop > azure-terraform-lab.
How do I add VS Code to my system PATH on Windows?
- In VS Code, click File in the top menu bar.
- Click New File.
- Save the file immediately with Ctrl+S and name it main.tf.
- Paste the following code into main.tf:
# Configure the Azure provider
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
required_version = ">= 1.2.0"
}
# Initialize the provider with default settings
provider "azurerm" {
features {}
}
# Create a resource group to hold all project resources
resource "azurerm_resource_group" "rg" {
name = "devops-terraform-rg"
location = "eastus"
}
What does this code do?
- The terraform block tells Terraform to download the azurerm provider from HashiCorp's registry. The ~> 4.0 constraint means any version from 4.0 up to (but not including) 5.0.
- The provider "azurerm" block initializes the provider. The empty features {} block is required by the Azure provider even when you do not need any special feature flags.
- The resource "azurerm_resource_group" "rg" block defines a resource group named devops-terraform-rg in the eastus region. This is the container that will hold all your Azure resources.
- Save main.tf with Ctrl+S.
Seeing red squiggles in VS Code?
VS Code may not recognize .tf files by default. You can ignore any syntax warnings for now. The file will work correctly when you run Terraform commands in the terminal.
Why am I seeing warnings in my .tf file?
✔️ Awesome, I've got everything!
Great. Make sure you have saved main.tf and you can see it listed in the VS Code Explorer sidebar.
ⓧ I'd like to double check the full code
Your main.tf should contain exactly this:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
required_version = ">= 1.2.0"
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "devops-terraform-rg"
location = "eastus"
}
Initialize Terraform and preview the plan
Before Terraform can create anything, it needs to download the Azure provider plugin. The terraform init command reads your configuration and fetches everything it needs.
- Switch back to your PowerShell terminal. Make sure you are inside the azure-terraform-lab folder.
- Download the Azure provider by running:
terraform init
You should see output ending with Terraform has been successfully initialized! This means Terraform downloaded the azurerm provider and created a .terraform directory and a .terraform.lock.hcl lock file.
Seeing an error during init?
If you see a message about missing configuration files, confirm you are in the azure-terraform-lab folder (run pwd to check). Your main.tf must be in the same directory where you run terraform init.
Help me troubleshoot my terraform init error
Now that Terraform has the provider, you can ask it what it plans to do without actually creating anything.
- Preview what Terraform will create by running:
terraform plan
At the bottom of the output, you should see Plan: 1 to add, 0 to change, 0 to destroy. This tells you Terraform wants to create exactly one resource: your resource group.
What is terraform plan?
The terraform plan command is a dry run. It compares your configuration to what currently exists in Azure (nothing yet) and shows what it would create, modify, or destroy. It never makes changes. This is your safety check before committing.
Apply the configuration and verify in Azure
The plan looks good. Time to make it real. The terraform apply command creates the resources in Azure.
Before you run this, what do you expect to happen? Will Terraform create the resource immediately, or will it ask you something first?
- Deploy the resource group by running:
terraform apply
- Terraform shows the same plan output, then asks Do you want to perform these actions? Type yes and press Enter.
You should see Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Your resource group now exists in Azure.
Why does Terraform ask for confirmation?
The yes prompt is a safety mechanism. In production, teams review the plan output carefully before approving changes. This prevents accidental deletions or modifications to live infrastructure.
- Verify the deployed state by running:
terraform show
You should see the resource group's details including its Azure resource ID, name (devops-terraform-rg), and location (eastus).
- Now confirm it in the Azure Portal. Open your browser and go to portal.azure.com.
- In the search bar at the top, type Resource groups and click the Resource groups service.
- You should see devops-terraform-rg listed. This is the resource Terraform just created from your code.
Don't see the resource group in the Portal?
Make sure you are signed in to the same Azure account you authenticated with az login. Check the subscription filter at the top of the Resource Groups page to ensure it is not filtering your subscription out.
Help me find my resource group in the Azure Portal
Your first piece of infrastructure is live in Azure, created entirely from code. Next up, you will add a virtual network and subnet inside this resource group.
Build a Virtual Network
Your resource group is deployed, but right now it is just an empty container. Every workload your team deploys into Azure needs network isolation to keep traffic separate and secure.
In this step, you will add the core networking layer: a virtual network with a subnet inside it. Think of the virtual network as your private address space in the cloud, and the subnet as a partition within it where you place resources.
In this step, get ready to:
- Add a virtual network resource to your Terraform configuration.
- Add a subnet inside the virtual network.
- Deploy both resources and verify them in the Azure Portal.
Add a virtual network resource
A virtual network defines the IP address range your Azure resources can use. You will give it a large address space and place it inside your existing resource group.
- In VS Code, open main.tf.
- Add the following code below your azurerm_resource_group block:
# Define the virtual network inside our resource group
resource "azurerm_virtual_network" "vnet" {
name = "devops-lab-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
What does this code do?
- address_space = ["10.0.0.0/16"] reserves over 65,000 IP addresses for your network. This is the full range your VNet can use.
- location and resource_group_name reference your existing resource group instead of hardcoding the values. If you change the resource group later, these update automatically.
- Save main.tf. The next chunk adds a subnet inside this network, then you will deploy both together.
Add a subnet resource
A subnet carves out a smaller IP range within the virtual network. Resources you deploy later will sit inside this subnet.
- Add the following code below your azurerm_virtual_network block in main.tf:
# Create a subnet within the virtual network
resource "azurerm_subnet" "subnet" {
name = "devops-lab-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
}
What does this code do?
- virtual_network_name references the VNet you just defined. Terraform uses this to know the subnet belongs inside that network.
- address_prefixes = ["10.0.1.0/24"] allocates 256 IP addresses from the larger VNet range. This is where your actual resources will live.
- Save main.tf.
Seeing red squiggles in VS Code?
If VS Code shows syntax warnings in your HCL, make sure each block has matching opening and closing braces. Check that your resource_group_name and virtual_network_name lines end without trailing commas.
help me fix syntax errors in my Terraform main.tf file
✔️ Awesome, I've got everything!
Great. Make sure you have saved main.tf with both new resource blocks.
ⓧ I'd like to double check the full code
Your main.tf should contain these blocks in order. Scroll up and confirm each one is present:
- The terraform block with required_providers (azurerm source and version) and required_version.
- The provider "azurerm" block with features {}.
- The azurerm_resource_group resource named "rg".
- The azurerm_virtual_network resource named "vnet" with address space 10.0.0.0/16.
- The azurerm_subnet resource named "subnet" with address prefix 10.0.1.0/24.
Deploy both resources and verify in the Portal
Both resources are defined. Time to preview and deploy them.
Before you run the next command, how many resources do you think Terraform will want to create? Remember it already deployed the resource group in the previous step.
- Preview the changes by running:
terraform plan
You should see Plan: 2 to add, 0 to change, 0 to destroy at the bottom. Terraform detected the two new resources (VNet and subnet) and will leave your existing resource group untouched.
- Deploy the resources by running:
terraform apply
- When Terraform asks Enter a value, type yes and press Enter to confirm.
You should see Apply complete! Resources: 2 added, 0 changed, 0 destroyed once it finishes.
- Open the Azure Portal in your browser.
- Navigate to Resource groups and click devops-terraform-rg.
- You should see devops-lab-vnet listed as a Virtual network resource inside the group.
- Click devops-lab-vnet, then click Subnets in the left menu. You should see devops-lab-subnet listed with address range 10.0.1.0/24.
Don't see the VNet in the Portal?
- Make sure terraform apply printed Apply complete! with no errors. If it failed, read the error message for a clue.
- Confirm you are looking inside the devops-terraform-rg resource group, not a different one.
- Try clicking the Refresh button at the top of the resource group page.
help me troubleshoot why my Terraform VNet is not appearing in the Azure Portal
Your networking layer is live. Next up, you will add a network security group to control what traffic is allowed in and out of your subnet.
Add a Network Security Group
Your virtual network is deployed, but it is completely open. Any resource you place inside that subnet has zero firewall rules protecting it.
A Network Security Group acts as a virtual firewall for your subnet. In this step, you'll create one with rules that allow SSH traffic and deny everything else.
In this step, get ready to:
- Add a Network Security Group resource with two security rules.
- Deploy the NSG to Azure and verify both rules appear in the Portal.
- Check whether your subnet is actually protected.
Add the NSG resource to your configuration
An NSG contains security rules that filter inbound and outbound traffic. Each rule has a priority number. Azure evaluates rules from lowest priority number first.
- In VS Code, open main.tf and add the following code below the azurerm_subnet resource:
# Network security group with two firewall rules
resource "azurerm_network_security_group" "nsg" {
name = "devops-lab-nsg"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
security_rule {
name = "AllowSSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "0.0.0.0/0"
destination_address_prefix = "*"
}
security_rule {
name = "DenyAllInbound"
priority = 200
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
What do these rules do?
The AllowSSH rule (priority 100) permits inbound TCP traffic on port 22 from any source. This is the standard SSH port.
The DenyAllInbound rule (priority 200) blocks all other inbound traffic. Azure evaluates the lowest priority number first, so SSH traffic matches rule 100 before hitting the deny-all at 200.
- Save main.tf.
Deploy the NSG to Azure
- Preview what Terraform will create by running this command:
terraform plan
You should see "1 to add" in the plan summary. Terraform detected the new NSG resource.
- Deploy the NSG by running:
terraform apply
- Type yes when prompted to confirm.
Terraform creates the NSG and reports "Apply complete! Resources: 1 added."
- Open the Azure Portal and navigate to your devops-terraform-rg resource group.
- Click on devops-lab-nsg in the resource list.
- Click Inbound security rules in the left menu.
You should see both AllowSSH and DenyAllInbound listed with their priorities.
Don't see the NSG in your resource group?
- Make sure terraform apply completed without errors. Check the terminal output for red error text.
- Confirm you are looking at the devops-terraform-rg resource group in the Portal, not a different one.
- Try refreshing the Portal page. Azure sometimes takes a few seconds to display new resources.
Help me debug why my NSG is not showing in the Azure Portal.
Check whether your subnet is protected
Your NSG is deployed with both rules. Before you move on, do you think your subnet is now protected by these firewall rules?
- In the Portal, while viewing the devops-lab-nsg resource, click Subnets in the left menu.
The Subnets section is empty. Your NSG exists, but it is not associated with your subnet. Traffic flows freely through the subnet with no filtering at all.
Why isn't the NSG protecting my subnet?
In Azure, creating an NSG and creating a subnet are independent actions. You must explicitly associate them with a separate resource. Without that association, the NSG's rules are never evaluated against traffic in the subnet.
This is a common gotcha. The security rules look correct, and the NSG is deployed, but nothing connects it to the subnet it should protect.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf with the NSG resource included.
ⓧ I'd like to double check the full code
Your main.tf should contain these resource blocks in order. Scroll up through your file and confirm each one is present:
- The terraform block with the azurerm provider requirement.
- The provider "azurerm" block with features {}.
- The azurerm_resource_group resource named rg.
- The azurerm_virtual_network resource named vnet.
- The azurerm_subnet resource named subnet.
- The azurerm_network_security_group resource named nsg with both AllowSSH and DenyAllInbound security rules.
You've uncovered a real-world security gap. The rules are correct, but they are not connected to anything. Next up, you'll wire the NSG to your subnet and refactor your configuration with variables.
Wire It All Together with Variables
In the last step, you discovered a critical gap. Your NSG exists with two security rules, but it is not associated with the subnet.
This step fixes both that security gap and another problem lurking in your code. Every value in main.tf is hardcoded. If your team needs a different region or naming convention, someone has to hunt through the file and change strings one by one. You will wire the NSG to the subnet, extract hardcoded values into reusable variables, and expose key resource IDs as outputs.
In this step, get ready to:
- Associate the NSG with the subnet to close the security gap.
- Extract hardcoded values into reusable Terraform variables.
- Create outputs that expose key resource identifiers.
Associate the NSG with the subnet
In Azure, creating a Network Security Group does not automatically attach it to anything. You need an explicit association resource that links the NSG to a specific subnet. Without it, traffic flows freely as if the NSG does not exist.
- In VS Code, open main.tf.
- Scroll to the bottom of the file (after the closing } of the NSG resource) and add this association resource:
resource "azurerm_subnet_network_security_group_association" "nsg_assoc" {
subnet_id = azurerm_subnet.subnet.id
network_security_group_id = azurerm_network_security_group.nsg.id
}
What does this resource do?
This resource tells Azure to apply the NSG's rules to the subnet. subnet_id points to the subnet that needs protection. network_security_group_id points to the NSG containing your AllowSSH and DenyAllInbound rules.
Once this resource exists, any VM or service deployed into the subnet will be governed by those rules.
- Save main.tf.
- Deploy the association by running this command:
terraform apply
- Type yes when prompted to confirm.
You should see Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Before you check the Portal, do you think the NSG will now appear under the subnet's settings? What changed between now and Step 4?
- Open the Azure Portal and navigate to your devops-terraform-rg resource group.
- Click on your subnet (open the VNet, then click Subnets in the left menu).
- Confirm that the Network security group column now shows your NSG name.
The security gap from Step 4 is closed. Your firewall rules now actively protect the subnet.
Still seeing no NSG under Subnets?
Make sure you saved main.tf before running terraform apply. Check the terminal output for errors. If it says the association already exists, the resource might have been created by a prior attempt.
help me troubleshoot my NSG subnet association in Terraform
Extract hardcoded values into variables
Your main.tf has strings like "devops-terraform-rg" and "eastus" scattered throughout. If your team needs to deploy the same infrastructure in a different region or with a different naming convention, they would need to edit every occurrence by hand.
Terraform variables solve this. You define a variable once in a separate file, give it a default value, and reference it everywhere with var.name. Changing one default updates every resource that uses it.
- Create a new file called variables.tf in the same folder as main.tf.
- Add the following variable definitions:
variable "resource_group_name" {
description = "Name of the Azure resource group"
type = string
default = "devops-terraform-rg"
}
variable "location" {
description = "Azure region for resources"
type = string
default = "eastus"
}
variable "project_prefix" {
description = "Prefix for all resource names"
type = string
default = "devops-lab"
}
variable "allowed_ssh_cidr" {
description = "CIDR block allowed to SSH"
type = string
default = "0.0.0.0/0"
}
What does this code do?
- Each variable block declares a named input with a type and a default value.
- resource_group_name and location replace the hardcoded strings in your resource group.
- project_prefix replaces the repeated "devops-lab" prefix in your VNet, subnet, and NSG names.
- allowed_ssh_cidr replaces the hardcoded "0.0.0.0/0" in your SSH rule so a team can restrict SSH access per environment.
- Save variables.tf.
Now you need to update main.tf to reference these variables instead of the hardcoded strings.
- In main.tf, find the azurerm_resource_group block.
- Find these two lines:
name = "devops-terraform-rg"
location = "eastus"
- Replace them with variable references so the block looks like this:
name = var.resource_group_name
location = var.location
- Next, find the name line in the azurerm_virtual_network block.
- Find this line:
name = "devops-lab-vnet"
- Replace it with:
name = "${var.project_prefix}-vnet"
- Find the name line in the azurerm_subnet block.
- Find this line:
name = "devops-lab-subnet"
- Replace it with:
name = "${var.project_prefix}-subnet"
- Find the name line in the azurerm_network_security_group block.
- Find this line:
name = "devops-lab-nsg"
- Replace it with:
name = "${var.project_prefix}-nsg"
- Finally, find the source_address_prefix line inside the AllowSSH rule.
- Find this line:
source_address_prefix = "0.0.0.0/0"
- Replace it with:
source_address_prefix = var.allowed_ssh_cidr
- Save main.tf.
- Confirm the refactor is safe by running:
terraform plan
You should see: No changes. Your infrastructure matches the configuration.
Why does it say no changes?
The variable defaults match the strings you replaced. Terraform resolves each variable to its default, compares the result to the deployed state, and sees no difference. This confirms your refactor is safe.
Seeing unexpected changes in the plan?
If Terraform wants to destroy and recreate resources, a default value does not match the original hardcoded string. Double-check that project_prefix defaults to "devops-lab" (not "devops-lab-" with a trailing dash).
help me fix my Terraform variable refactor
✔️ Awesome, I've got everything!
Great. Make sure both files are saved before continuing.
ⓧ I'd like to double check the full code
Your variables.tf should look exactly like this:
variable "resource_group_name" {
description = "Name of the Azure resource group"
type = string
default = "devops-terraform-rg"
}
variable "location" {
description = "Azure region for resources"
type = string
default = "eastus"
}
variable "project_prefix" {
description = "Prefix for all resource names"
type = string
default = "devops-lab"
}
variable "allowed_ssh_cidr" {
description = "CIDR block allowed to SSH"
type = string
default = "0.0.0.0/0"
}
For main.tf, scroll up through your file and confirm each of these changes is in place, in order:
- The resource group block uses var.resource_group_name and var.location.
- The VNet name is "${var.project_prefix}-vnet".
- The subnet name is "${var.project_prefix}-subnet".
- The NSG name is "${var.project_prefix}-nsg".
- The AllowSSH rule uses var.allowed_ssh_cidr for source_address_prefix.
- The azurerm_subnet_network_security_group_association block is at the bottom of the file.
Create outputs and apply the final configuration
Terraform outputs let you surface key values after an apply. Your team can use outputs to grab resource IDs without digging through the Portal or the state file.
- Create a new file called outputs.tf in the same folder as main.tf.
- Add these output definitions:
output "resource_group_name" {
description = "Name of the created resource group"
value = azurerm_resource_group.rg.name
}
output "vnet_name" {
description = "Name of the virtual network"
value = azurerm_virtual_network.vnet.name
}
output "subnet_id" {
description = "ID of the subnet"
value = azurerm_subnet.subnet.id
}
output "nsg_id" {
description = "ID of the network security group"
value = azurerm_network_security_group.nsg.id
}
What does this code do?
- Each output block names a value that Terraform prints after every apply.
- resource_group_name and vnet_name expose human-readable names for quick reference.
- subnet_id and nsg_id expose the full Azure resource IDs that other Terraform projects or scripts might need.
- Save outputs.tf.
- Apply the full configuration by running:
terraform apply
- Type yes when prompted to confirm.
Terraform applies the outputs and prints them at the end of the run. You should see all four output values displayed in your terminal.
Before you run the next command, what do you expect terraform output to print?
- View the outputs any time by running:
terraform output
You should see all four values printed: the resource group name, VNet name, subnet ID, and NSG ID.
Seeing an error about outputs?
Make sure the value in each output block references the exact resource name. For example, azurerm_resource_group.rg.name must match the label "rg" in your resource block. A typo here causes a reference error.
help me fix my Terraform outputs
✔️ Awesome, I've got everything!
Great. Make sure all three files are saved.
ⓧ I'd like to double check the full code
Your outputs.tf should look exactly like this:
output "resource_group_name" {
description = "Name of the created resource group"
value = azurerm_resource_group.rg.name
}
output "vnet_name" {
description = "Name of the virtual network"
value = azurerm_virtual_network.vnet.name
}
output "subnet_id" {
description = "ID of the subnet"
value = azurerm_subnet.subnet.id
}
output "nsg_id" {
description = "ID of the network security group"
value = azurerm_network_security_group.nsg.id
}
Your infrastructure is fully deployed, secured, and structured like a real team project. You have variables for flexibility, outputs for visibility, and an NSG that actually protects the subnet. Next up, you can take this further with the Secret Mission.
Secret mission
Refactor into a Reusable Terraform Module
Your job description says '5+ years developing Terraform modules.' Modules are how real teams package and reuse infrastructure patterns. Can you restructure your flat configuration into a module that any teammate could call from their own project?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running or delete them entirely. This project uses only free Azure resources (resource groups, VNets, subnets, and NSGs cost nothing), so there are no ongoing charges.
Resources you used:
- 1 Azure Resource Group (devops-terraform-rg).
- 1 Virtual Network.
- 1 Subnet.
- 1 Network Security Group.
- 1 NSG-Subnet Association.
- Local project folder on your Desktop.
Keep everything running
No action needed. Choose this if you are still actively experimenting with Terraform or plan to extend this project further.
- All Azure resources in this project are free. They will not generate any charges regardless of how long they exist.
- Your local Terraform files and state remain on your Desktop for future reference.
- Terraform CLI and Azure CLI remain installed on your machine and available for other projects.
Pause - I'll come back to this later
Since all resources are free, there is nothing to pause. You can leave everything as-is and pick up where you left off.
- Sign out of Azure CLI to revoke your local session by running:
- Your Terraform state file remembers what was deployed. When you return, run terraform plan to see the current state.
az logout
When you return, run az login to re-authenticate and continue from where you left off.
Delete - I don't want to use this again
Remove all project resources and start fresh. This is the recommended option to keep your Azure subscription clean.
Destroy all Azure resources
- In PowerShell, navigate to your project folder and run:
terraform destroy
- Type yes when prompted. All five Azure resources (resource group, VNet, subnet, NSG, and association) are removed within seconds.
Delete local project files
- Remove the entire project folder from your Desktop by running:
Remove-Item -Recurse -Force ~\Desktop\devops-terraform
Uninstall Azure CLI
- Open Settings on your Windows machine.
- Go to Apps, then Installed apps.
- Search for Azure CLI. The entry appears as Microsoft CLI 2.0 for Azure.
- Select it and click Uninstall.
Remove Terraform CLI
- Delete the terraform.exe file from the folder where you extracted it (e.g. C:\terraform).
- Remove that folder from your system PATH. Open System Properties > Advanced > Environment Variables.
- In the System variables section, select Path and click Edit.
- Find the entry pointing to your Terraform folder, select it, and click Delete. Click OK to save.
Sign out of Azure
- Sign out of your Azure CLI session by running:
az logout
Nice Work!
Nice Work!
Nice work! You just deployed a fully secured Azure virtual network entirely through Terraform configuration files.
You've learned how to:
- Deploy a resource group, virtual network, subnet, and network security group to Azure using the full Terraform workflow (init, plan, apply).
- Discover that creating an NSG does not automatically protect a subnet, and fix it with an explicit azurerm_subnet_network_security_group_association resource.
- Refactor hardcoded values into variables and expose key resource IDs as outputs for production-style Terraform code.
- Secret Mission: Restructure your project into a reusable Terraform module that your team could call from any project.
Ready to quiz yourself?