Popular Searches

Deploying Azure VMs with Terraform to Save Money

azure logo

Most of us have done it—deployed temporary VMs to Azure. Something that many of us also have done is to forget to remove resources attached to those VMs when deleting them. This can lead to unwanted costs upwards to thousands of dollars if you’re not careful.

In this article, we’ll learn how to deploy VMs to Azure and to remove every trace of them when we are finished by using Terraform.


You need to fulfill a few prerequisites to be able to follow along in this article:

  • Have the Azure CLI installed.
  • Be an administrator over an Azure Subscription (or similar), which allows you to use it to create resource groups and resources.

With these out of the way, let’s start by downloading and installing Terraform.

Installing Terraform

Terraform can be installed in a a few different ways. In Windows, the easiest way is to use choco:

choco install terraform

Installing Terraform using choco.

On a Macintosh, it is available by using Brew:

brew install terraform

More information about installing Terraform is available in the Terraform documentation.

Assuming that everything has gone well with the installation, you can confirm that it works by running the command terraform version.

Confirm Terraform works by running the command "terraform version".

Now that you have confirmed that terraform works on your machine, it’s time to authenticate to the Azure subscription through the Azure CLI.


To authenticate and set the subscription in the Azure CLI, you need to login. Open a terminal and type:

az login

And then, after authenticating, list all your subscriptions by entering:

az account list

If you have several subscriptions available, copy the subscription id of the correct one and enter:

az account set --subscription="COPIED_SUBSCRIPTION_ID"

You have now set the Azure CLI to use your subscription. (This is the subscription that terraform will use later as well.) It is now time to create the terraform template that we will use to deploy our VM.

Creating the Terraform Template

The Terraform template is a simple text file with the file ending .tf. It has a JSON-like syntax, and can be read and generated easily. The template consists of mainly two parts: the providers (that handle the communication to the service in which you wish to deploy to), and the resources that the provider creates.

We start out by creating a file called vm.tf in an empty dir. Terraform will need an empty dir so that it can write the state of the configuration later on.

Now, open that file with your favorite text editor, and add the provider:

provider "azurerm" {
  version   = "=2.11.0"
	features {}  

This will tell Terraform that you want to use the AzureRM provider (for creating azure resources), and that it should be of version 2.11. It is important to specify the version number because the functionality between different versions may vary heavily.

After you have written that into the text file, it’s time to add our Azure resource group. This is seen by Terraform as a resource as well:

resource "azurerm_resource_group" "resourcegroup" {
        name = "test-vm"
        location = "westus"
        tags = {
            project = "some_test_project"

The code above creates a resource group named “test-vm” that is located in the western U.S. region. It also adds a tag with the project name on it.

To run a VM successfully, we also need a network. And because Terraform is made for Infrastructure as Code (IaC), it’s available as a resource as well:

resource "azurerm_virtual_network" "network" {
    name                = "${azurerm_resource_group.resourcegroup.name}-network"
    address_space       = [""]
    location            = azurerm_resource_group.resourcegroup.location
    resource_group_name = azurerm_resource_group.resourcegroup.name

    tags = azurerm_resource_group.resourcegroup.tags

This creates a virtual network. It also uses a variable in the name parameter. If you look carefully, you will see that it refers to the resource group that we defined above:


That means that the virtual network will be given the name test-vm-network. We do the same for the location, resource group name, and tags as well.

Next up, it is time to define the subnet that we will place the VM in, using the same methods using variables as before:

resource "azurerm_subnet" "subnet" {
    name                 = "${azurerm_resource_group.resourcegroup.name}-subnet"
    resource_group_name  = azurerm_resource_group.resourcegroup.name
    virtual_network_name = azurerm_virtual_network.network.name
    address_prefix       = ""

This creates a subnet called test-vm-subnet.

Now, let’s define the NIC that the VM is going to use as well:

resource "azurerm_network_interface" "vm1-nic" {
    name                        = "vm1-NIC"
    location                    = azurerm_resource_group.resourcegroup.location
    resource_group_name         = azurerm_resource_group.resourcegroup.name

    ip_configuration {
        name                          = "vm1-NicConfiguration"
        subnet_id                     = "${azurerm_subnet.subnet.id}"
        private_ip_address_allocation = "static"
        private_ip_address            = ""

    tags = azurerm_resource_group.resourcegroup.tags

In this case, we’ll use a static IP address, referring to the subnet by using a variable.

And, at last but not least, the definition of the VM itself:

resource "azurerm_virtual_machine" "vm-1" {
  name                  = "vm1"
  location              = "${azurerm_resource_group.resourcegroup.location}"
  resource_group_name   = "${azurerm_resource_group.resourcegroup.name}"
  network_interface_ids = ["${azurerm_network_interface.vm1-nic.id}"]
  vm_size               = "Standard_B1ms"

  delete_os_disk_on_termination = true

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  storage_os_disk {
    name              = "vm1-osdisk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  os_profile {
    computer_name  = "vm-1"
    admin_username = "demoadmin"
    admin_password = "$om3s3cretPassWord"

  os_profile_windows_config {
      enable_automatic_upgrades = "true"
      provision_vm_agent = "true"

  tags = azurerm_resource_group.resourcegroup.tags

The code above will provision an Azure VM running Windows Server 2019, using the resources that we defined previously. It will have an admin with the username “demoadmin” that has the password “$om3s3cretPassWord”. The rest of the parameters are pretty self-explanatory, and you can find a lot more of themin the Terraform AzureRM documentation.

Now, all that is left is to save the .tf-file by deploying this template to Azure using TerraForm!

Initiating and Deploying with TerraForm

Now that we have the template in order, open up a terminal, and go to the directory that you saved the .tf-file to:

cd c:\temp\terraform

We must now initiate TerraForm. This will download the resource module for AzureRM and look after errors in the .tf-file:

Initiate Terraform, which downloads the resource module and looks for errors in the .tf file.

After the initialization has finished, you are ready to apply the template by running terraform apply.

terraform apply

TerraForm will now create a new resource group in Azure, networks, subnets, and eventually the VM itself. The state and all the resources it has created are stored in the .terraform folder of your current directory. So, don’t remove that folder if you want to remove the resources cleanly later on!

After you are done with your test vm and wish to destroy it, just run:

terraform destroy

This will remove all disks, nics, subnets, resource groups, and such that it created when running terraform apply, and you don’t have to worry about forgetting to remove the fragments left in terms of Azure Resources.


Using TerraForm only for minor tasks like this is incredibly neat. It proves that you don’t have to have a fully implemented Infrastructure as Code (IaC) to use it productively. It also brings down the cost by removing unused resources in Azure. Hopefully, this has proven to be of use to you, and even started your journey towards IaC!

Adam Bertram Adam Bertram
Adam Bertram is a 20+ year veteran of IT and an experienced online business professional. He’s a consultant, Microsoft MVP, blogger, trainer, published author and content marketer for multiple technology companies. Catch up on Adam’s articles at adamtheautomator.com, connect on LinkedIn, or follow him on Twitter at @adbertram. Read Full Bio »

The above article may contain affiliate links, which help support CloudSavvy IT.