Deploying infrastructure shouldn't be a copy-paste headache. Here's how to make your Azure VM setup repeatable and clean using Terraform.

When I started using Terraform to manage infrastructure on Azure, one thing became clear very quickly: writing the same Terraform config every time gets old fast.

In this blog, I'm going to show you how to create an Azure Virtual Machine using a reusable Terraform script. By the end of this post, you'll have a modular setup you can use across projects—without rewriting the whole thing each time.


What You’ll Need

Before we begin, make sure you’ve got the following:


Step 1: Create the Folder Structure

Let’s keep things clean.

mkdir terraform-azure-vm
cd terraform-azure-vm

mkdir modules
mkdir modules/vm

We’ll keep our reusable VM code inside modules/vm and the environment-specific code in the root folder.


Step 2: Build the Reusable VM Module

Inside modules/vm/, create these files:

main.tf

resource "azurerm_network_interface" "nic" {
  name                = "${var.vm_name}-nic"
  location            = var.location
  resource_group_name = var.resource_group_name

  ip_configuration {
    name                           = "internal"
    subnet_id                      = var.subnet_id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_windows_virtual_machine" "vm" {
  name                = var.vm_name
  resource_group_name = var.resource_group_name
  location            = var.location
  size                = var.vm_size
  admin_username      = var.admin_username
  admin_password      = var.admin_password
  network_interface_ids = [azurerm_network_interface.nic.id]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }
}

variables.tf

variable "vm_name" {}
variable "resource_group_name" {}
variable "location" {}
variable "subnet_id" {}
variable "vm_size" {}
variable "admin_username" {}
variable "admin_password" {}

Step 3: Use the Module in Your Environment

Go to the root folder and create:

main.tf

provider "azurerm" {
  features {}
}

module "vm" {
  source              = "./modules/vm"
  vm_name             = "dev-vm"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  subnet_id           = azurerm_subnet.subnet.id
  vm_size             = "Standard_B1s"
  admin_username      = "azureuser"
  admin_password      = "MySecureP@ssw0rd123"
}

resource "azurerm_resource_group" "rg" {
  name     = "dev-rg"
  location = "East US"
}

resource "azurerm_virtual_network" "vnet" {
  name                = "dev-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_subnet" "subnet" {
  name                 = "dev-subnet"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]
}

Step 4: Don’t Forget the Backend (Optional but recommended)

For team use or remote state management, configure a backend.tf file using Azure Storage Account.


Step 5: Deploy It!

terraform init
terraform plan
terraform apply

Done! Your Azure VM is up and running.


Wrapping Up

With this modular approach, the next time you need a VM? Just tweak a few inputs. No more rewriting long scripts or fighting with syntax every time.

Let me know in the comments if you'd like to see a Linux version, auto-shutdown scripts, or cost-saving tricks next!


Happy Terraforming!