All case studiesConfiguration Management

Terraform on Azure

A Linux VM plus the full network stack Azure requires — built from scratch in code, with a Spot fallback for regions that are capacity-constrained.

TerraformAzureInfrastructure as Code

The challenge

Unlike AWS, Azure has no default network — you cannot create a VM until an entire networking stack exists. And free-tier B-series sizes are capacity-restricted on some subscriptions, so a naive apply simply fails.

Our approach

We provision the stack in dependency order: resource group, virtual network, subnet, public IP, network security group, NIC, the NSG-to-NIC association, and finally the Linux VM. Authentication is SSH-key-only (password login disabled). For subscriptions where the free B1s size is unavailable, we ship a working Spot-instance recipe as a committed, secret-free variables file.

Terraform CLIinit · plan · applyCloud providerCLI profile authlookupcreateData sources (read-only)VM · network · firewallssh_commandoutput

Technical specifics

  • Terraform ≥ 1.5.0 with azurerm ~> 4.0; eight resources declared explicitly (no count/for_each).
  • VNet 10.0.0.0/16 and subnet 10.0.1.0/24, with an NSG opening SSH/HTTP/HTTPS.
  • Default size Standard_B1s (1 vCPU, 1 GiB) on Ubuntu 24.04 LTS; Spot fallback Standard_B2ats_v2.
  • Spot configured for capacity-only eviction (max bid -1, deallocate policy) to cut cost.
  • Serialized apply with -parallelism=1; auth via an az login session and the subscription id from the CLI.

Example configuration

The network stack is built explicitly before the VM; a Spot block turns on capacity-only eviction where the free size is unavailable.

network.tfhcl
resource "azurerm_virtual_network" "vnet" {
  name                = "freetier-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                 = "freetier-subnet"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]
}
vm.tf (Spot fallback) + commandshcl
resource "azurerm_linux_virtual_machine" "vm" {
  name           = var.name
  size           = "Standard_B2ats_v2"   # Spot fallback; free tier = Standard_B1s
  priority        = "Spot"
  max_bid_price   = -1                    # evict on capacity only, never on price
  eviction_policy = "Deallocate"
  admin_username  = "azureuser"
  # ...network_interface_ids, os_disk, ssh key (password auth disabled)...
}

# export ARM_SUBSCRIPTION_ID="$(az account show --query id -o tsv)"
# terraform apply -auto-approve -parallelism=1

Outcome

One Ubuntu 24.04 VM with a public IP reachable on SSH/HTTP/HTTPS, fully declared in version-controlled files, reproducible, and torn down with a single command — with Spot pricing available where it helps.

Have a similar challenge?

We deliver this work end to end — from first commit to a secure, live deploy. Let’s talk about yours.