{"id":1223,"date":"2026-10-12T12:52:06","date_gmt":"2026-10-12T12:52:06","guid":{"rendered":"https:\/\/www.devopsschool.com\/tutorials\/?p=1223"},"modified":"2026-10-12T13:00:32","modified_gmt":"2026-10-12T13:00:32","slug":"terraform-lab-azure-resources-demo","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/tutorials\/terraform-lab-azure-resources-demo\/","title":{"rendered":"Terraform Lab &#8211; Azure &#8211; Resources &#8211; VMS &amp; Netowork Demo"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>Level:<\/strong>&nbsp;Absolute Beginner<br><strong>Terraform version:<\/strong>&nbsp;1.15.9<br><strong>AzureRM Provider:<\/strong>&nbsp;5.9.0<br><strong>Approach:<\/strong>&nbsp;Each lab builds on the previous lab.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These are four complete Terraform configurations designed for students to execute sequentially.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Learning Path<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Lab 1 \u2014 Azure Resource Group:<\/strong>\u00a0Create your first Azure resource.<\/li>\n\n\n\n<li><strong>Lab 2 \u2014 Azure Networking:<\/strong>\u00a0Add VNet, subnet, NSG and subnet association.<\/li>\n\n\n\n<li><strong>Lab 3 \u2014 Windows Server VM:<\/strong>\u00a0Add Windows Server 2025, public IP and RDP access.<\/li>\n\n\n\n<li><strong>Lab 4 \u2014 Ubuntu Server VM:<\/strong>\u00a0Add Ubuntu Server 26.04 LTS, public IP and SSH access.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">1. Selected Azure configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I checked Microsoft&#8217;s Azure Marketplace documentation and Canonical&#8217;s official Azure image catalog.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Setting<\/th><th class=\"has-text-align-left\" data-align=\"left\">Selected value<\/th><\/tr><\/thead><tbody><tr><td>Azure region<\/td><td><code>eastus<\/code>&nbsp;(East US)<\/td><\/tr><tr><td>Resource Group<\/td><td><code>rg-terraform-lab-eastus<\/code><\/td><\/tr><tr><td>VNet<\/td><td><code>vnet-terraform-lab<\/code><\/td><\/tr><tr><td>VNet CIDR<\/td><td><code>10.20.0.0\/16<\/code><\/td><\/tr><tr><td>Subnet<\/td><td><code>snet-vms<\/code><\/td><\/tr><tr><td>Subnet CIDR<\/td><td><code>10.20.1.0\/24<\/code><\/td><\/tr><tr><td>Windows OS<\/td><td>Windows Server 2025 Datacenter<\/td><\/tr><tr><td>Ubuntu OS<\/td><td>Ubuntu Server 26.04 LTS<\/td><\/tr><tr><td>VM size<\/td><td><code>Standard_B2s_v2<\/code><\/td><\/tr><tr><td>Authentication<\/td><td>Username + password<\/td><\/tr><tr><td>Provider<\/td><td><code>hashicorp\/azurerm<\/code>&nbsp;5.9.x<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Windows clarification:<\/strong>&nbsp;Microsoft&#8217;s September 2026 Marketplace catalog lists Windows Server 2025 as the newest Windows Server release, not Windows Server 2026.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ubuntu:<\/strong>&nbsp;Canonical publishes&nbsp;<code>Canonical:ubuntu-26_04-lts:server:latest<\/code>&nbsp;as the Ubuntu 26.04 LTS Gen2 image.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The selected&nbsp;<code>Standard_B2s_v2<\/code>&nbsp;is a burstable VM with 2 vCPUs and 8 GiB RAM, suitable for these introductory labs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These image IDs are supported by the published catalogs. Availability in your particular subscription and region must still be checked before deployment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Prerequisites \u2014 Complete once<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You need an Azure subscription, Azure CLI, Terraform, and permissions to create resources.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Log in and select your subscription:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az login\n\naz account list --output table\n\n<em># Select the subscription you want to use<\/em>\naz account set --subscription \"YOUR_SUBSCRIPTION_ID\"\n\n<em># Required for AzureRM provider 5.x<\/em>\nexport ARM_SUBSCRIPTION_ID=\"$(az account show --query id -o tsv)\"\n\n<em># Check active subscription<\/em>\naz account show --output table\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Have your subscription administrator ensure&nbsp;<code>Microsoft.Compute<\/code>&nbsp;and&nbsp;<code>Microsoft.Network<\/code>&nbsp;resource providers are registered. AzureRM 5.x no longer automatically registers them by default.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check the images directly in Azure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Windows Server 2025 Datacenter<\/em>\naz vm image show \\\n  --location eastus \\\n  --urn MicrosoftWindowsServer:WindowsServer:2025-datacenter:latest \\\n  --output table\n\n<em># Ubuntu Server 26.04 LTS<\/em>\naz vm image show \\\n  --location eastus \\\n  --urn Canonical:ubuntu-26_04-lts:server:latest \\\n  --output table\n\n<em># Check selected VM size and regional restrictions<\/em>\naz vm list-skus \\\n  --location eastus \\\n  --size Standard_B2s_v2 \\\n  --all \\\n  --output table\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Before Lab 3, obtain your public IPv4 address:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -4 https:\/\/api.ipify.org\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You&#8217;ll use it to restrict RDP and SSH access to your own computer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Important classroom rules<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>No Terraform input variables (<code>variable<\/code>\u00a0or\u00a0<code>.tfvars<\/code>).<\/li>\n\n\n\n<li>No data sources.<\/li>\n\n\n\n<li>No Terraform modules or local values.<\/li>\n\n\n\n<li>No output blocks.<\/li>\n\n\n\n<li>Only native AzureRM resources, plus the required Terraform and provider configuration.<\/li>\n\n\n\n<li>Each lab provides a complete replacement\u00a0<code>main.tf<\/code>.<\/li>\n\n\n\n<li>Use the same directory and Terraform state for all four labs. Do not create a new directory and state for each.<\/li>\n\n\n\n<li>Replace the sample VM passwords with your own strong passwords and use your real public IPv4\u00a0<code>\/32<\/code>\u00a0for the firewall rules. Carry these changes forward when advancing to the next lab.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The examples use passwords because this is a teaching requirement. Terraform stores these passwords in its state, so keep&nbsp;<code>main.tf<\/code>, state files, and saved plans private. Use SSH keys and secret management for real deployments.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 1 \u2014 Create an Azure Resource Group<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Level:<\/strong>&nbsp;Beginner<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Objective:<\/strong>&nbsp;Learn the basic structure of a Terraform configuration and create your first Azure resource.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>main.tf \u2014 Complete code<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># ============================================\n# LAB 1 - CREATE AZURE RESOURCE GROUP\n# ============================================\n\n# Terraform configuration\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\n# Azure provider configuration\nprovider \"azurerm\" {\n  features {}\n}\n\n# Create Resource Group\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-terraform-lab-eastus\"\n  location = \"eastus\"\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Execute Lab 1<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Create a directory and save the code above as&nbsp;<code>main.tf<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir azure-terraform-labs\ncd azure-terraform-labs\n\n<em># Save the code as main.tf, then:<\/em>\nterraform init\nterraform fmt\nterraform validate\nterraform plan\nterraform apply\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enter&nbsp;<code>yes<\/code>&nbsp;when Terraform asks for confirmation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected result:<\/strong>&nbsp;<code>1 added, 0 changed, 0 destroyed<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verify in Azure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az group show \\\n  --name rg-terraform-lab-eastus \\\n  --output table\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Student learns:<\/strong>&nbsp;Provider configuration, resource blocks,&nbsp;<code>terraform init<\/code>,&nbsp;<code>plan<\/code>,&nbsp;<code>apply<\/code>, and Terraform state.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 2 \u2014 Create Azure Networking<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Objective:<\/strong>&nbsp;Extend Lab 1 to create a VNet, subnet, Network Security Group (NSG), and associate the NSG with the subnet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>main.tf \u2014 Complete code, includes Lab 1<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># ============================================\n# LAB 2 - CREATE AZURE NETWORKING\n# Includes all resources from Lab 1\n# ============================================\n\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\n# 1. Resource Group\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-terraform-lab-eastus\"\n  location = \"eastus\"\n}\n\n# 2. Virtual Network\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-terraform-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  address_space = &#91;\"10.20.0.0\/16\"]\n}\n\n# 3. Subnet\nresource \"azurerm_subnet\" \"vms\" {\n  name                 = \"snet-vms\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n\n  address_prefixes = &#91;\"10.20.1.0\/24\"]\n}\n\n# 4. Network Security Group\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-terraform-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\n# 5. Associate NSG with the Subnet\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.vms.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Execute Lab 2<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Replace the existing&nbsp;<code>main.tf<\/code>&nbsp;with the complete Lab 2 code, keeping the same working directory and state.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform fmt\nterraform validate\nterraform plan\nterraform apply\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected result:<\/strong>&nbsp;<code>4 added, 0 changed, 0 destroyed<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The existing Resource Group remains unchanged.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verify the networking:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Verify Virtual Network<\/em>\naz network vnet show \\\n  --resource-group rg-terraform-lab-eastus \\\n  --name vnet-terraform-lab \\\n  --output table\n\n<em># Verify Subnet<\/em>\naz network vnet subnet show \\\n  --resource-group rg-terraform-lab-eastus \\\n  --vnet-name vnet-terraform-lab \\\n  --name snet-vms \\\n  --output table\n\n<em># Verify Network Security Group<\/em>\naz network nsg show \\\n  --resource-group rg-terraform-lab-eastus \\\n  --name nsg-terraform-lab \\\n  --output table\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Student learns:<\/strong>&nbsp;Azure VNet, CIDR, subnets, NSGs, resource references, resource dependencies, and how Terraform adds infrastructure to an existing state.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 3 \u2014 Create a Windows Server 2025 Virtual Machine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Objective:<\/strong>&nbsp;Add a Windows Server VM to the infrastructure created in Labs 1 and 2.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>New resources:<\/strong>&nbsp;Public IP, network interface, RDP security rule, and Windows VM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The RDP rule uses&nbsp;<code>203.0.113.10\/32<\/code>, a documentation-only IP address. Replace this with your own public IPv4 address followed by&nbsp;<code>\/32<\/code>&nbsp;before running Terraform. Also change the demonstration password.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>main.tf \u2014 Complete code, includes Labs 1 + 2<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># ============================================\n# LAB 3 - CREATE WINDOWS SERVER 2025 VM\n# Includes all resources from Labs 1 and 2\n# ============================================\n\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\n# --------------------------------------------\n# 1. Resource Group\n# --------------------------------------------\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-terraform-lab-eastus\"\n  location = \"eastus\"\n}\n\n# --------------------------------------------\n# 2. Virtual Network\n# --------------------------------------------\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-terraform-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  address_space = &#91;\"10.20.0.0\/16\"]\n}\n\n# --------------------------------------------\n# 3. Subnet\n# --------------------------------------------\nresource \"azurerm_subnet\" \"vms\" {\n  name                 = \"snet-vms\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n\n  address_prefixes = &#91;\"10.20.1.0\/24\"]\n}\n\n# --------------------------------------------\n# 4. Network Security Group\n# --------------------------------------------\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-terraform-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\n# --------------------------------------------\n# 5. Associate NSG with Subnet\n# --------------------------------------------\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.vms.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\n# --------------------------------------------\n# 6. Allow RDP from Student's Public IP\n# --------------------------------------------\nresource \"azurerm_network_security_rule\" \"rdp\" {\n  name                        = \"Allow-RDP\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"3389\"\n  source_address_prefix       = \"203.0.113.10\/32\" # REPLACE with your IP\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\n# --------------------------------------------\n# 7. Windows VM Public IP\n# --------------------------------------------\nresource \"azurerm_public_ip\" \"windows\" {\n  name                = \"pip-win2025-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  allocation_method = \"Static\"\n  sku               = \"Standard\"\n}\n\n# --------------------------------------------\n# 8. Windows VM Network Interface\n# --------------------------------------------\nresource \"azurerm_network_interface\" \"windows\" {\n  name                = \"nic-win2025-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.vms.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.windows.id\n  }\n}\n\n# --------------------------------------------\n# 9. Windows Server 2025 Virtual Machine\n# --------------------------------------------\nresource \"azurerm_windows_virtual_machine\" \"windows\" {\n  name                = \"vm-win2025-01\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  size           = \"Standard_B2s_v2\"\n  admin_username = \"labadmin\"\n\n  # Training only - replace with a unique strong password\n  admin_password = \"ChangeMe!2026WinLab#\"\n\n  network_interface_ids = &#91;\n    azurerm_network_interface.windows.id\n  ]\n\n  # OS Disk\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n\n  # Windows Server 2025 Datacenter Image\n  source_image_reference {\n    publisher = \"MicrosoftWindowsServer\"\n    offer     = \"WindowsServer\"\n    sku       = \"2025-datacenter\"\n    version   = \"latest\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Execute Lab 3<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Replace&nbsp;<code>main.tf<\/code>&nbsp;with the above, preserve any earlier changes, and then execute:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform fmt\nterraform validate\nterraform plan\nterraform apply\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected result:<\/strong>&nbsp;<code>4 added, 0 changed, 0 destroyed<\/code>, assuming you have carried forward the earlier configuration unchanged.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Verify and connect to Windows<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Get the public IP:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az network public-ip show \\\n  --resource-group rg-terraform-lab-eastus \\\n  --name pip-win2025-lab \\\n  --query ipAddress \\\n  --output tsv\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check the VM:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az vm show \\\n  --resource-group rg-terraform-lab-eastus \\\n  --name vm-win2025-01 \\\n  --show-details \\\n  --output table\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then use Windows App (Remote Desktop) on your Mac, or Remote Desktop Connection on Windows.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Setting<\/th><th class=\"has-text-align-left\" data-align=\"left\">Value<\/th><\/tr><\/thead><tbody><tr><td>Host<\/td><td>Public IP returned above<\/td><\/tr><tr><td>Port<\/td><td>3389<\/td><\/tr><tr><td>Username<\/td><td><code>labadmin<\/code><\/td><\/tr><tr><td>Password<\/td><td>Your replacement Windows password<\/td><\/tr><tr><td>Operating System<\/td><td>Windows Server 2025 Datacenter<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Student learns:<\/strong>&nbsp;VM creation, image selection, managed OS disks, network interfaces, public IP addresses, NSG inbound rules, and RDP authentication.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab 4 \u2014 Create an Ubuntu Server 26.04 Virtual Machine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Objective:<\/strong>&nbsp;Extend the existing infrastructure by adding an Ubuntu Linux VM with password-based SSH authentication.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At the end of this lab, students will have both Windows and Ubuntu VMs running in the same Azure Virtual Network.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>New resources:<\/strong>&nbsp;Linux public IP, Linux network interface, SSH security rule, and Ubuntu VM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>main.tf \u2014 Complete code, includes Labs 1 + 2 + 3<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># ============================================\n# LAB 4 - CREATE UBUNTU SERVER 26.04 VM\n# Includes all resources from Labs 1, 2, 3\n# ============================================\n\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\n# --------------------------------------------\n# 1. Resource Group\n# --------------------------------------------\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-terraform-lab-eastus\"\n  location = \"eastus\"\n}\n\n# --------------------------------------------\n# 2. Virtual Network\n# --------------------------------------------\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-terraform-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  address_space = &#91;\"10.20.0.0\/16\"]\n}\n\n# --------------------------------------------\n# 3. Subnet\n# --------------------------------------------\nresource \"azurerm_subnet\" \"vms\" {\n  name                 = \"snet-vms\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n\n  address_prefixes = &#91;\"10.20.1.0\/24\"]\n}\n\n# --------------------------------------------\n# 4. Network Security Group\n# --------------------------------------------\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-terraform-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\n# --------------------------------------------\n# 5. Associate NSG with Subnet\n# --------------------------------------------\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.vms.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\n# --------------------------------------------\n# 6. Allow RDP from Student's Public IP\n# --------------------------------------------\nresource \"azurerm_network_security_rule\" \"rdp\" {\n  name                        = \"Allow-RDP\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"3389\"\n  source_address_prefix       = \"203.0.113.10\/32\" # REPLACE with your IP\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\n# --------------------------------------------\n# 7. Allow SSH from Student's Public IP\n# --------------------------------------------\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"Allow-SSH\"\n  priority                    = 110\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = \"203.0.113.10\/32\" # REPLACE with your IP\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\n# --------------------------------------------\n# 8. Windows VM Public IP\n# --------------------------------------------\nresource \"azurerm_public_ip\" \"windows\" {\n  name                = \"pip-win2025-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  allocation_method = \"Static\"\n  sku               = \"Standard\"\n}\n\n# --------------------------------------------\n# 9. Windows VM Network Interface\n# --------------------------------------------\nresource \"azurerm_network_interface\" \"windows\" {\n  name                = \"nic-win2025-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.vms.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.windows.id\n  }\n}\n\n# --------------------------------------------\n# 10. Windows Server 2025 VM\n# --------------------------------------------\nresource \"azurerm_windows_virtual_machine\" \"windows\" {\n  name                = \"vm-win2025-01\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  size           = \"Standard_B2s_v2\"\n  admin_username = \"labadmin\"\n\n  # Keep the same replacement password from Lab 3\n  admin_password = \"ChangeMe!2026WinLab#\"\n\n  network_interface_ids = &#91;\n    azurerm_network_interface.windows.id\n  ]\n\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n\n  source_image_reference {\n    publisher = \"MicrosoftWindowsServer\"\n    offer     = \"WindowsServer\"\n    sku       = \"2025-datacenter\"\n    version   = \"latest\"\n  }\n}\n\n# --------------------------------------------\n# 11. Ubuntu VM Public IP\n# --------------------------------------------\nresource \"azurerm_public_ip\" \"ubuntu\" {\n  name                = \"pip-ubuntu2604-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  allocation_method = \"Static\"\n  sku               = \"Standard\"\n}\n\n# --------------------------------------------\n# 12. Ubuntu VM Network Interface\n# --------------------------------------------\nresource \"azurerm_network_interface\" \"ubuntu\" {\n  name                = \"nic-ubuntu2604-lab\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.vms.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.ubuntu.id\n  }\n}\n\n# --------------------------------------------\n# 13. Ubuntu Server 26.04 LTS VM\n# --------------------------------------------\nresource \"azurerm_linux_virtual_machine\" \"ubuntu\" {\n  name                = \"vm-ubuntu2604-01\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n\n  size           = \"Standard_B2s_v2\"\n  admin_username = \"labadmin\"\n\n  # Training only - replace with a unique strong password\n  admin_password = \"ChangeMe!2026LinuxLab#\"\n\n  # Enable SSH password authentication\n  disable_password_authentication = false\n\n  network_interface_ids = &#91;\n    azurerm_network_interface.ubuntu.id\n  ]\n\n  # OS Disk\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n\n  # Ubuntu Server 26.04 LTS Gen2 Image\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-26_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Execute Lab 4<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Replace the Lab 3 configuration with this complete Lab 4 configuration. Before applying, preserve your custom Windows password and RDP source IP, and set your own Ubuntu password and SSH source IP.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform fmt\nterraform validate\nterraform plan\nterraform apply\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected result:<\/strong>&nbsp;<code>4 added, 0 changed, 0 destroyed<\/code>, provided the existing Windows VM and earlier resource configuration have been preserved.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Verify and connect to Ubuntu<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Retrieve the public IP:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az network public-ip show \\\n  --resource-group rg-terraform-lab-eastus \\\n  --name pip-ubuntu2604-lab \\\n  --query ipAddress \\\n  --output tsv\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Connect using SSH:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh labadmin@YOUR_UBUNTU_PUBLIC_IP\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enter the Ubuntu password you configured.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once connected, verify the operating system:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \/etc\/os-release\nhostnamectl\nip addr\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Student learns:<\/strong>&nbsp;Linux VM provisioning, Ubuntu Marketplace images, password authentication, SSH, and running multiple operating systems in one Azure VNet.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Final infrastructure after all four labs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Final Azure resources (East US):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Azure Subscription\n\u2514\u2500\u2500 Resource Group: rg-terraform-lab-eastus\n    \u2514\u2500\u2500 VNet: vnet-terraform-lab (10.20.0.0\/16)\n        \u2514\u2500\u2500 Subnet: snet-vms (10.20.1.0\/24)\n            \u251c\u2500\u2500 NSG: Restricted inbound RDP 3389 and SSH 22\n            \u251c\u2500\u2500 Windows Server 2025 VM (NIC + Public IP, RDP)\n            \u2514\u2500\u2500 Ubuntu Server 26.04 LTS VM (NIC + Public IP, SSH)\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">5. Expected Terraform resource count<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Lab<\/th><th class=\"has-text-align-left\" data-align=\"left\">New Terraform resources<\/th><th class=\"has-text-align-left\" data-align=\"left\">Cumulative<\/th><\/tr><\/thead><tbody><tr><td>Lab 1 \u2014 Resource Group<\/td><td>1<\/td><td>1<\/td><\/tr><tr><td>Lab 2 \u2014 Networking<\/td><td>4<\/td><td>5<\/td><\/tr><tr><td>Lab 3 \u2014 Windows VM<\/td><td>4<\/td><td>9<\/td><\/tr><tr><td>Lab 4 \u2014 Ubuntu VM<\/td><td>4<\/td><td>13<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Resource counts exclude Azure-managed supporting objects such as the VM OS disks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">After Lab 4, run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform state list\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see 13 Terraform-managed resource addresses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, clean up the lab to avoid ongoing Azure VM and public IP charges:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -destroy\nterraform destroy\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Important:<\/strong>&nbsp;These configurations have been checked against published Terraform, Microsoft, and Canonical documentation, but have not been applied to a live Azure subscription. Actual deployment is subject to your subscription&#8217;s regional image availability, VM quota, Azure policies, and permissions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This gives students a progressive path through resource groups, networking, Windows, and Ubuntu while learning how Terraform maintains and extends infrastructure through a single state.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Level:&nbsp;Absolute BeginnerTerraform version:&nbsp;1.15.9AzureRM Provider:&nbsp;5.9.0Approach:&nbsp;Each lab builds on the previous lab. These are four complete Terraform configurations designed for students to execute sequentially. Learning Path 1. Selected Azure&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1223","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/comments?post=1223"}],"version-history":[{"count":2,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1223\/revisions"}],"predecessor-version":[{"id":1226,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1223\/revisions\/1226"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/media?parent=1223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/categories?post=1223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/tags?post=1223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}