This project demonstrates how to create an Azure Resource Group and a Virtual Network using Terraform and the AzureRM provider.
Make sure you have the following installed:
- Terraform
- Azure CLI
Open PowerShell and run:
az loginVerify your account and subscription:
az account showTerraform requires a subscription ID.
Set it using:
$env:ARM_SUBSCRIPTION_ID="your-subscription-id"Example:
$env:ARM_SUBSCRIPTION_ID="c928cbac-ea4a-4c7b-8970-3773f4175f80"Create a file named variables.tf and add:
variable "resource_group_name" {
description = "Name of the Azure Resource Group"
type = string
default = "mtc-resources"
}
variable "location" {
description = "Azure region where resources will be created"
type = string
default = "Canada Central"
}
variable "environment" {
description = "Environment tag (e.g., dev, prod)"
type = string
default = "dev"
}
variable "virtual_network_name" {
description = "Name of the Virtual Network"
type = string
default = "mtc-network"
}
variable "address_space" {
description = "Address space for the Virtual Network"
type = list(string)
default = ["10.123.0.0/16"]
}Create a file named main.tf and add:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=4.1.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "mtc-rg" {
name = var.resource_group_name
location = var.location
tags = {
environment = var.environment
}
}
resource "azurerm_virtual_network" "mtc-vn" {
name = var.virtual_network_name
address_space = var.address_space
location = var.location
resource_group_name = var.resource_group_name
tags = {
environment = var.environment
}
}terraform initterraform planYou should see:
Plan: 2 to add, 0 to change, 0 to destroy.
terraform applyType:
yesterraform destroy- The Virtual Network defines a private network inside Azure.
- The address space specifies the IP range used within that network.
- Additional components such as subnets and virtual machines can be added later.
terraform state list
.
├── main.tf
├── variables.tf
├── README.md
└── .gitignore
After applying your configuration, you can inspect the current state of your infrastructure using the following commands.
terraform state listExample output:
azurerm_resource_group.mtc-rg
azurerm_virtual_network.mtc-vn
This shows all resources currently tracked by Terraform.
terraform state show azurerm_resource_group.mtc-rgExample output:
# azurerm_resource_group.mtc-rg:
resource "azurerm_resource_group" "mtc-rg" {
id = "/subscriptions/.../resourceGroups/mtc-resources"
location = "canadacentral"
managed_by = null
name = "mtc-resources"
tags = {
"environment" = "dev"
}
}
This displays detailed information about a specific resource stored in the Terraform state.
- Terraform state represents the current infrastructure managed by Terraform.
- The
state listcommand shows all tracked resources. - The
state showcommand provides detailed attributes for a specific resource. - Some values (like
id) are generated by Azure after deployment.
Terraform allows you to preview and remove all managed resources.
terraform plan --destroyThis command shows what resources will be deleted without actually removing them.
Example output:
Plan: 0 to add, 0 to change, 2 to destroy.
terraform apply --destroyType:
yesThis command deletes all resources managed by Terraform in the current configuration.
terraform plan --destroyis used to review changes before deletion.terraform apply --destroypermanently removes all managed resources.- This is equivalent to running
terraform destroy.
Terraform automatically creates a backup of the state file named:
terraform.tfstate.backup
This file contains the previous state before the last change.
If a mistake is made and the current state becomes incorrect, you can restore it from the backup:
copy terraform.tfstate.backup terraform.tfstate- The backup file is created automatically by Terraform.
- It stores the last known good state before changes were applied.
- Restoring the backup can help recover from accidental modifications.
- Always ensure Terraform is not running when replacing the state file.
- State files may contain sensitive information and should not be committed to version control.
A subnet is a smaller network inside a Virtual Network. It allows you to organize and control resources within your network.
Update your main.tf file by adding:
resource "azurerm_subnet" "mtc-subnet" {
name = "mtc-subnet"
resource_group_name = azurerm_resource_group.mtc-rg.name
virtual_network_name = azurerm_virtual_network.mtc-vn.name
address_prefixes = ["10.123.1.0/24"]
}After running:
terraform planYou should see:
Plan: 1 to add, 0 to change, 0 to destroy.
- A subnet must be created inside a Virtual Network.
virtual_network_namelinks the subnet to the VNet.address_prefixesdefines the IP range for the subnet.- The subnet range must be within the Virtual Network address space.
In this example:
- Virtual Network:
10.123.0.0/16 - Subnet:
10.123.1.0/24
The subnet is a smaller portion of the main network.
A Network Security Group (NSG) is used to control inbound and outbound network traffic for Azure resources.
Update your main.tf file by adding:
resource "azurerm_network_security_group" "mtc-sg" {
name = "mtc-sg"
location = azurerm_resource_group.mtc-rg.location
resource_group_name = azurerm_resource_group.mtc-rg.name
tags = {
environment = "dev"
}
}Add a rule to define allowed traffic:
resource "azurerm_network_security_rule" "mtc-dev-rule" {
name = "mtc-dev-rule"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.mtc-rg.name
network_security_group_name = azurerm_network_security_group.mtc-sg.name
}After running:
terraform planYou should see:
Plan: 2 to add, 0 to change, 0 to destroy.
- A Network Security Group (NSG) acts as a firewall for your network.
- Security rules define what traffic is allowed or denied.
direction = "Inbound"means incoming traffic is controlled.access = "Allow"permits traffic matching the rule.protocol = "Tcp"applies the rule to TCP traffic.- Using
"*"allows all ports and all IP addresses. prioritydetermines rule order (lower number = higher priority).- The rule is associated with the NSG using
network_security_group_name.
This example creates a basic rule that allows all inbound TCP traffic. In real scenarios, rules should be more restrictive.
After creating a Network Security Group (NSG), it must be associated with a subnet or network interface to take effect.
Update your main.tf file by adding:
resource "azurerm_subnet_network_security_group_association" "mtc-sga" {
subnet_id = azurerm_subnet.mtc-subnet.id
network_security_group_id = azurerm_network_security_group.mtc-sg.id
}After running:
terraform planYou should see:
Plan: 1 to add, 0 to change, 0 to destroy.
- An NSG does not apply to any resources until it is associated.
- This resource links the NSG to a subnet.
subnet_ididentifies the subnet where the rules will apply.network_security_group_ididentifies the NSG to attach.- Once associated, all resources inside the subnet are affected by the NSG rules.
This step ensures that the defined security rules are enforced on the network.
A Public IP allows Azure resources to be accessible from the internet.
Update your main.tf file by adding:
resource "azurerm_public_ip" "mtc-ip" {
name = "mtc-ip"
resource_group_name = azurerm_resource_group.mtc-rg.name
location = azurerm_resource_group.mtc-rg.location
allocation_method = "Static"
sku = "Standard"
tags = {
environment = "dev"
}
}After running:
terraform planYou should see:
Plan: 1 to add, 0 to change, 0 to destroy.
- A Public IP enables communication between Azure resources and the internet.
sku = "Standard"is used by default in newer AzureRM provider versions.- Standard Public IPs require
allocation_method = "Static". - The IP address is assigned at creation time and remains the same.
- A Public IP is typically associated with a Network Interface or Load Balancer.
- This resource alone does not expose anything until it is attached to another resource.
This step prepares your infrastructure for external access.
A Network Interface connects a Virtual Machine to a Virtual Network and allows it to communicate with other resources.
Update your main.tf file by adding:
resource "azurerm_network_interface" "mtc-nic" {
name = "mtc-nic"
location = azurerm_resource_group.mtc-rg.location
resource_group_name = azurerm_resource_group.mtc-rg.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.mtc-subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.mtc-ip.id
}
tags = {
environment = "dev"
}
}After running:
terraform planYou should see:
Plan: 1 to add, 0 to change, 0 to destroy.
- A Network Interface (NIC) connects resources (such as virtual machines) to a network.
subnet_idlinks the NIC to a subnet inside the Virtual Network.private_ip_address_allocation = "Dynamic"lets Azure assign a private IP automatically.- The private IP comes from the subnet range (e.g.,
10.123.1.0/24). public_ip_address_idattaches a Public IP for external access.- Without a NIC, a Virtual Machine cannot communicate with the network.
This step prepares the network connection for future resources such as virtual machines.
A Virtual Machine (VM) is a compute resource that runs an operating system and applications in Azure.
Update your main.tf file by adding:
resource "azurerm_linux_virtual_machine" "mtc-vm" {
name = "mtc-vm"
resource_group_name = azurerm_resource_group.mtc-rg.name
location = azurerm_resource_group.mtc-rg.location
size = "Standard_B1s"
admin_username = "adminuser"
network_interface_ids = [
azurerm_network_interface.mtc-nic.id,
]
admin_ssh_key {
username = "adminuser"
public_key = file("~/.ssh/mtcazurekey.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
}After running:
terraform planYou should see:
Plan: 1 to add, 0 to change, 0 to destroy.
- A Virtual Machine provides compute resources in Azure.
size = "Standard_B1s"defines the VM size (CPU, memory).admin_usernameis used to log into the VM.network_interface_idsconnects the VM to the network.- The VM uses the previously created Network Interface, which includes both private and public connectivity.
admin_ssh_keyenables secure SSH access using a public key.file("~/.ssh/mtcazurekey.pub")reads the SSH public key from your local machine.os_diskdefines storage settings for the VM.source_image_referencespecifies the operating system image (Ubuntu 22.04 in this case).
After deployment, you can connect to the VM using SSH:
ssh adminuser@<public-ip-address>Replace <public-ip-address> with the Public IP created earlier.
- The SSH key pair must exist on your machine.
- The
.pubfile contains the public key used by Azure. - The private key remains on your local machine and is used for authentication.
This step creates a fully functional virtual machine connected to your network.
After the Virtual Machine is deployed, you can connect to it using SSH.
Run the following command in PowerShell:
ssh -i ~/.ssh/mtcazurekey adminuser@<public-ip-address>Replace <public-ip-address> with the Public IP created earlier.
Example:
ssh -i ~/.ssh/mtcazurekey adminuser@20.48.255.134If the connection is successful, you will see a message similar to:
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux ...)
- The
-iflag specifies the private SSH key used for authentication. - The username must match the one defined in the Terraform configuration (
adminuser). - The Public IP is assigned by Azure and used for external access.
- The private IP (e.g.,
10.123.1.4) is used internally within the Virtual Network. - SSH uses port 22, which must be allowed in the Network Security Group.
After login, you can run basic commands:
whoamihostnameip aThese commands help verify that you are connected to the correct virtual machine.
This confirms that the infrastructure is deployed correctly and accessible.
After connecting to the Virtual Machine, you can verify the operating system version.
Run the following command inside the VM:
lsb_release -aNo LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.5 LTS
Release: 22.04
Codename: jammy
- This confirms that the VM is running Ubuntu.
- The version matches the image defined in Terraform:
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}"jammy"is the codename for Ubuntu 22.04.- This step verifies that the correct OS image was deployed.
This confirms that the Virtual Machine was created successfully with the expected configuration.
Exit VM
adminuser@mtc-vm:~$ exit
logout
Connection to 20.48.255.134 closed.
In this project, the following infrastructure was created and tested using Terraform:
- Resource Group
- Virtual Network (VNet)
- Subnet
- Network Security Group (NSG) and security rule
- Subnet and NSG association
- Public IP Address
- Network Interface (NIC)
- Linux Virtual Machine (Ubuntu)
The deployment was verified by:
- Running
terraform planandterraform apply - Connecting to the Virtual Machine using SSH
- Confirming the Ubuntu version installed on the VM
All resources were successfully removed using:
terraform destroyThis ensures no cloud resources remain running after testing.
This project demonstrates a complete end-to-end infrastructure deployment in Azure using Terraform, including networking, security, and compute resources.
The Virtual Machine was successfully provisioned and accessed remotely, confirming that all components were configured correctly.