Building an Azure Virtual Machine with Terraform: A Step-by-Step Guide


🌐 Introduction

If you’ve worked with AWS before, you might already know how powerful Terraform can be for defining infrastructure as code (IaC). But did you know the same principles apply seamlessly to Azure? In this post, we’ll walk through how to use Terraform to create a Linux virtual machine in Microsoft Azure, step by step.

Whether you’re a cloud beginner or an experienced engineer exploring multi-cloud deployments, this example will help you understand the building blocks of provisioning infrastructure in Azure.


🧱 1. What Terraform Does

Terraform is an Infrastructure as Code (IaC) tool that lets you describe your infrastructure in simple configuration files and then automatically build it in the cloud.
Instead of manually creating VMs and networks in the Azure Portal, you write a .tf file, run a few commands, and Terraform does the rest.

Think of it like version-controlling your cloud infrastructure.


⚙️ 2. Prerequisites

Before you start:

  • Install Terraform
  • Install the Azure CLI
  • Run az login to authenticate to your Azure account
  • Have an SSH key pair ready (~/.ssh/id_rsa.pub)

🧩 3. Setting Up Your Terraform Files

We’ll create two simple files:

  • providers.tf – defines the Azure provider
  • main.tf – contains the infrastructure definition

🧰 providers.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }

  required_version = ">= 1.6.0"
}

provider "azurerm" {
  features {}
}

This block tells Terraform that we’ll be using Azure and which provider plugin to download.


☁️ main.tf

Here’s a minimal setup to create:

  • Resource group
  • Virtual network and subnet
  • Public IP
  • Network security group
  • Network interface
  • Linux VM
resource "azurerm_resource_group" "rg" {
  name     = "rg-terraform-demo"
  location = "eastus"
}

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-demo"
  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                 = "subnet-demo"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_public_ip" "public_ip" {
  name                = "publicip-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Static"
}

resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "allow_ssh"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    destination_port_range     = "22"
    source_port_range          = "*"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_interface" "nic" {
  name                = "nic-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "nic-ipconfig"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.public_ip.id
  }
}

resource "azurerm_linux_virtual_machine" "vm" {
  name                = "vm-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  size                = "Standard_B1s"
  admin_username      = "azureuser"
  network_interface_ids = [azurerm_network_interface.nic.id]

  admin_ssh_key {
    username   = "azureuser"
    public_key = file("~/.ssh/id_rsa.pub")
  }

  os_disk {
    name                 = "osdisk-demo"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  disable_password_authentication = true
}

output "public_ip" {
  value = azurerm_public_ip.public_ip.ip_address
}


🚀 4. Deploying Your Azure VM

Run the following commands in your terminal:

terraform init
terraform plan
terraform apply

Terraform will show you what it plans to build — type yes to confirm.
After a few minutes, you’ll see your VM’s public IP in the output.

Connect to it via SSH:

ssh azureuser@<public_ip>


🧠 5. Wrap-Up

Congratulations! 🎉 You’ve just provisioned a Linux VM in Azure with Terraform.
You now have the foundation to automate:

  • App deployments
  • Storage and networking setup
  • Scalable infrastructure pipelines

From here, try adding:

  • Tags for cost tracking
  • Port 80/443 rules for web servers
  • Auto-shutdown policies

Infrastructure as Code makes it easy to replicate, version, and scale your environments with minimal effort — whether it’s AWS, Azure, or Google Cloud.

Leave a comment