Terraform and Azure (Infrastructure as Code) – Part 1

During Summer 2020 I wrote a few blog posts about Terraform and gave a few examples in my geektechstuff AWS environment. Over the Autumn and Winter of 2020, and the Spring of 2021 I expanded my Terraform knowledge and experience a lot, and one of the points I think I need to blog about is how Terraform is platform agnostic, i.e. Terraform can create (and destroy!) objects within AWS, Google Cloud Platform, VMWare VSphere, Microsoft Azure and many more.

So with this blog post I am going to discuss using Terraform from within Microsoft Windows to build Azure infrastructure. If you want to follow along you will need access to an Azure account / subscription.

Note: You can use Terraform on Linux/Mac to build infrastructure within Azure but I want to show that Terraform can run on Windows platform as well as work with cloud providers other than AWS (which I’ve blogged about previously).

Azure CLI

For this blog post I am using PowerShell as my shell. If you have not already got the Azure CLI then now is the time to install it. Visit https://docs.microsoft.com/en-us/cli/azure/install-azure-cli , choose the operating system you are using, download the Azure CLI and install. The Azure CLI allows for a shell to login to an Azure account and also allows for interaction with Azure resources, and saves having to open a web browser to get some details.

Once the Azure CLI is installed use the command:

az login

To log into your Azure account. On successful log in the Azure CLI should return details about the Azure subscriptions your account has access to, make a note of the “isDefault” subscription.


Terraform can be downloaded from https://www.terraform.io/downloads.html , choose your operating system and if you are running 32-bit or 64 bit. For Windows this is an application (.exe) within a compressed folder (.zip). Extract the terraform.exe and save it someone convenient, i.e. C:\Terraform.

Terraform will need adding to the Windows system environment path, to do this:

  • Open Control Panel
  • Open System
  • Open “Advanced System Settings”
  • Open “Environment Variables”
  • Edit “Path” and add the path to Terraform, e.g. C:\Terraform

Check Terraform is working correctly using the command:

terraform -version

If it fails, close and reopen your PowerShell session.

With Azure CLI and Terraform both ready to use, it is time to deploy some Azure infrastructure.

Azure Terraform Files

Terraform with Azure uses the same files as Terraform with AWS, the difference is that Azure and AWS call similar objects by different names, e.g. Azure has VNETS, AWS has VPCs. Azure has Virtual Machines, AWS has EC2 instances. Azure has Network Security Groups, AWS has Security Groups (well, that one is very similar in name). With this in mind my test project is made up of:


Containing the main part of my infrastructure as code, detailing what objects I want to build.


Containing what types of variables I want to use and which objects can be referenced to a variable.


Containing the variable values to set when building.

Please see my earlier posts about Terraform which discuss variables and state files.

Deploying a Virtual Machine

For this test project I’m going to deploy a virtual machine into Azure. Azure, just like AWS, offers a list of possible operating systems that are already available for use with virtual machines. To grab a list of the operating systems and their details run the following PowerShell command:

az vm image list

Make a note of the offer, publisher and sku of the operating system that you want to use.

The main.tf file needs to contain a few things, feel free to adjust as you see fit (e.g. the version requirements). Anywhere I have used var.xxxxx is being pulled through from terraform.tfvars file. For the tags I’ve wrote them out once and then used … (3 dots) to represent them afterwards.

A provider block:

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

A provider details block:

provider "azurerm" {
    features {}

A resource group, which will help keep all the objects involved in the build together:

resource "azurerm_resource_group" "rg" {
    name = var.resourcegroup
    location = var.location

    tags = {
        Owner = "GeekTechStuff"
        Terraform = " True"
        Purpose = " Test"

A virtual network (vnet) to contain the objects network:

resource "azurerm_virtual_network" "vnet" {
    name = "vnet-${var.location}"
    address_space = var.vnet_address_space
    location = var.location
    resource_group_name = azurerm_resource_group.rg.name

    tags = {

A subnet of the vnet:

resource "azurerm_subnet" "subnet" {
    name = "snet=${var.location}"
    virtual_network_name = azurerm_virtual_network.vnet.name
    resource_group_name = azurerm_resource_group.rg.name
    address_prefixes = varn.snet_address_space

    tags = {

If external access is needed on the virtual machine it will need a public IP address:

resource "azurerm_public_ip" "pubip" {
    name = "pubip-${var.servername}"
    location = var.location
    resource_group_name = azurerm_resource_group.rg.name
    allocation_method = "Dynamic"

    tags = {

A network interface to connect to the virtual machine so that it can connect to the subnet and use the public IP address:

resource "azurerm_network_interface" "nic" {
    name = "nic-01-${var.servername}"
    location = var.location
    resource_group_name = azurerm_resource_group.rg.name

    ip_configuration {
        name = "nic-cfg-${var.servername}"
        subnet_id = azurerm_subnet.subnet.id
        private_ip_address_allocation = "Dynamic"
        public_ip_address_id = azurerm_public_ip.pubip.id

    tags = {

A network security group to allow ingress, note my_IP_ADDRESS should be replaced with an appropriate IP address and CIDR range. Also, for increased security the desination_address_prefix could be set to the IP of the virtual machine:

resource "azurerm_network_security_group" "sec_group" {
    name = "sg-${var.servername}"
    location = var.location
    resource_group_name = azurerm_resource_group.rg.name

    security_rule {
        name = "SSH"
        priority = 1001
        direction = "Inbound"
        access = "Allow"
        protocol = "Tcp"
        source_port_range = "*"
        destination_port_range = "22"
        source_address_prefix = "my_IP_ADDRESS/32"
        destination_address_prefix = "*"
    tags = {

With the networking created the virtual machine can be defined. There are currently (at least) 2 ways to create a Linux virtual machine in Azure. For this first post on Azure / Terraform I will show one way (azurerm_virtual_machine) and then a different way in my second post. In the below I use a username and password, and leave password authentication on – it’s not recommended to do this for any easy to guess password / any amount of lengthy time. In my second post I will give details on how to use a SSH key instead.

resource "azurerm_virutal_machine" "vm" {
    name = var.servername
    location = var.location
    resource_group_name = azurerm_resource_group.rg.name
    network_interface_ids = [azurerm_network_interface.nic.id]
    vm_size = var.vm_size

    storage_os_disk {
        name = "os-${var.servname}-disk"
        caching = "ReadWrite"
        create_option = "FromImage"

    storage_image_reference {
        publisher = var.os.publisher
        offer = var.os.offer
        sku = var.os.sku
        version = var.os.version

    os_profile {
        computer_name = var.servername
        admin_username = var.admin_username
        admin_password = var.admin_password

    os_profile_linux_config {
        disable_password_authentication = false

    tags = {

Variables.tf defines what type (e.g. string, list) each variable is with a default (in case I forget to define it) and the terraform.tfvars contains:

resourcegroup = "geektechstuff_rg"
servername = "geektechstuff_test"
location = "uksouth"
admin_username = "adminuser"
admin_password = "notrecommendedleavingpasswordinexample:)"
vnet_address_space = [""]
os = {
    publisher = "Canonical"
    offer = "UbuntuServer"
    sku = "18.04-LTS"
    version = "latest"
vm_size = "Standard_B2s"

These variables should create an Ubuntu 18.04 LTS running on a standard B2s size virtual machine. Azure keeps a list and details about the virtual machines at https://docs.microsoft.com/en-us/azure/virtual-machines/sizes .

2 thoughts on “Terraform and Azure (Infrastructure as Code) – Part 1

Comments are closed.