Wanting to get started using Terraform in Azure? This blog post is for you, it will contain all that you will require for getting started with Terraform on Azure!
What will the blog post contain?
- Intro to Terraform
- Terraform Stages
- Common Terraform commands
- Terraform directory setup
- Deploying your Azure first resources via Terraform
- Storing your terraform state file in a remote location (Azure Storage Account)
Intro to Terraform
Terraform is a very common IaC (Infrastructure as code) toolset; itself is cloud agnostic and has providers for a number of cloud providers including Microsoft Azure
Why IaC?
Check out my blog post on this to find out Why Infrastructure as Code (IaC) ?
Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.
https://www.terraform.io/intro/index.html
Terraform Stages
Terraform has four main stages throughout the development and deployment of Terraform
- Initialisation: The start of deploying any Terraform will begin with the initialisation stage
- Planning: You can create and view a Terraform plan prior to creating any Azure resources using Terraform
- Applying: Once the plan has been reviewed, you can run the apply stage to deploy the required Azure resources
- Destroying: Not used all the time; but you can run the destroy stage to remove any Azure related resources/changes
Initialisation -> Planning -> Applying -> Destroying
Common Terraform commands
We’ve covered the Terraform stages – lets now look at the commands used to run these stages
- terraform init – Used to initialise the Terraform directory
- terraform plan – Shows the Terraform plan
- terraform apply – Applies the Terraform code
- terraform destroy – Destroys resources deployed via Terraform
Terraform Directory Setup
Terraform directories can be configured in multiple ways – depending on the project, customer, team etc! I will cover a standard terraform directory setup
terraform_folder
└──main.tf
└──variables.tf
└──locals.tf
└──providers.tf
└──develop.tfvars
The folder setup above will be used in this blog post, lets have a closer look at each file
- main.tf – The main Azure resources to deploy
- variables.tf – Containing variables that will be used in main.tf
- providers.tf – Mentioned at the start of the blog post, multiple Terraform providers can be used to deploy various Azure resources
- locals.tf – A local value; usually a common value that may never change between deployments
- develop.tfvars – .tfvars are great, they are used to define variables between different Terraform environments
Deploying your first Azure resources using Terraform
You would write Terraform; as you would write any sort of configuration language code, in an editor of your choice while storing any changes in a version controlled respository, whether it is local or remote!
I highly recommend you store your configuration code in a remote repository!
So… as you are making progress on your Terraform configuration; you would be running several Terraform Plans to confirm your syntax is correct and help to iron-out any errors in relation to syntax or misconfiguration(s) that you are noticing. Doing this, will ensure that your Terraform configuration is coming together as expected.
The plan is looking good? Time to commit that change! Once you commit to the required change, you run Terraform Apply which will add/remove or change infrastructure that you have defined within your Terraform configuration.
This core workflow is a continuous loop throughout any project; the next change or additional/removal you want to make – you will follow the exact same process

Terraform Folder breakdown
main.tf
The Azure resources that will be deployed
- Resource Group
- Storage Account
data "azurerm_client_config" "current" {}
terraform {
backend "local" {
}
}
# Create Resource Group
resource "azurerm_resource_group" "tamops" {
name = var.resource_group_name
location = var.location
}
# Create Storage Account
resource "azurerm_storage_account" "example" {
name = var.storage_account_name
resource_group_name = azurerm_resource_group.tamops.name
location = azurerm_resource_group.tamops.location
account_tier = "Standard"
account_replication_type = "LRS"
tags = {
environment = local.environment
}
}
The terraform backend for now will be local – terraform will store its terraform.state file locally
Notice the reference to var? This will be looking for variables – you will see the variables declared in variables.tf
Similar to this, reference to local – local values will be declared in local.tf
local.tf
locals {
environment = "development"
}
variables.tf
variable "location" {
type = string
default = "UK South"
description = "default resources location"
}
variable "resource_group_name" {
type = string
description = "resource group name"
}
variable "storage_account_name" {
type = string
description = "storage account name"
}
providers.tf
Calling/declaring the azurerm provider
provider "azurerm" {
# The "feature" block is required for AzureRM provider 2.x.
# If you're using version 1.x, the "features" block is not allowed.
version = "~>2.0"
features {}
}
develop.tfvars
.tfvars for the “develop” environment, in this file it will contain the resource group & storage account name
resource_group_name = "tamopstfrg"
storage_account_name = "tamopstfsa"
The files are all created, ready to deploy some Terraform!
Deploying your first Azure Resources using Terraform
Prior to running terraform, you are required to log into the specific Azure subscription using Az CLI
# Run az login & enter your Azure credentials
az login
# Select your Azure Subscription
az account set -s "thomasthorntoncloud"
Once you have logged in & configured subscription above – init Terraform within the folder
thomasthorntoncloud@Thomass-MBP terraform % terraform init
Initializing the backend...
Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "~> 2.0"...
- Installing hashicorp/azurerm v2.62.0...
- Installed hashicorp/azurerm v2.62.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Various files & folders relating to Terraform initialising are created as part of this

With Terraform now initialised – run a terraform plan, notice the reference to -var-file=develop.tfvars – this references the var-file that will be used for develop.tfvars
thomasthorntoncloud@Thomass-MBP terraform % terraform plan -var-file=develop.tfvars
Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.tamops will be created
+ resource "azurerm_resource_group" "tamops" {
+ id = (known after apply)
+ location = "uksouth"
+ name = "tamopstfrg"
}
# azurerm_storage_account.example will be created
+ resource "azurerm_storage_account" "example" {
+ access_tier = (known after apply)
+ account_kind = "StorageV2"
+ account_replication_type = "LRS"
+ account_tier = "Standard"
+ allow_blob_public_access = false
+ enable_https_traffic_only = true
+ id = (known after apply)
+ is_hns_enabled = false
+ large_file_share_enabled = (known after apply)
+ location = "uksouth"
+ min_tls_version = "TLS1_0"
+ name = "tamopstfsa"
+ nfsv3_enabled = false
+ primary_access_key = (sensitive value)
+ primary_blob_connection_string = (sensitive value)
+ primary_blob_endpoint = (known after apply)
+ primary_blob_host = (known after apply)
+ primary_connection_string = (sensitive value)
+ primary_dfs_endpoint = (known after apply)
+ primary_dfs_host = (known after apply)
+ primary_file_endpoint = (known after apply)
+ primary_file_host = (known after apply)
+ primary_location = (known after apply)
+ primary_queue_endpoint = (known after apply)
+ primary_queue_host = (known after apply)
+ primary_table_endpoint = (known after apply)
+ primary_table_host = (known after apply)
+ primary_web_endpoint = (known after apply)
+ primary_web_host = (known after apply)
+ resource_group_name = "tamopstfrg"
+ secondary_access_key = (sensitive value)
+ secondary_blob_connection_string = (sensitive value)
<<<<<< Removed part of storage account due to size of output >>>>>>>
Plan: 2 to add, 0 to change, 0 to destroy.
Reviewing the plan, we can see 2 resources will be added with 0 change or destroy
Time to apply!
thomasthorntoncloud@Thomass-MBP terraform % terraform apply -var-file=develop.tfvars
azurerm_resource_group.tamops: Creating...
azurerm_resource_group.tamops: Creation complete after 1s [id=/subscriptions/XXXXXXX/resourceGroups/tamopstfrg]
azurerm_storage_account.example: Creating...
azurerm_storage_account.example: Still creating... [10s elapsed]
azurerm_storage_account.example: Still creating... [20s elapsed]
azurerm_storage_account.example: Still creating... [30s elapsed]
azurerm_storage_account.example: Still creating... [40s elapsed]
azurerm_storage_account.example: Still creating... [50s elapsed]
azurerm_storage_account.example: Creation complete after 50s [id=/subscriptions/XXXXXXX/resourceGroups/tamopstfrg/providers/Microsoft.Storage/storageAccounts/tamopstfsa]

Resource group & storage account now deployed!
We can now destroy these two resources by terraform destroy
thomasthorntoncloud@Thomass-MBP terraform % terraform destroy -var-file=develop.tfvars
azurerm_storage_account.example: Destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg/providers/Microsoft.Storage/storageAccounts/tamopstfsa]
azurerm_storage_account.example: Destruction complete after 5s
azurerm_resource_group.tamops: Destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 10s elapsed]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 20s elapsed]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 30s elapsed]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 40s elapsed]
azurerm_resource_group.tamops: Destruction complete after 45s
We have now ran through the 4 common Terraform commands
- terraform init
- terraform plan
- terraform apply
- terraform destroy
Throughout these 4 commands, you may have noticed two files also created within your terraform directory
terraform.tfstate & terraform.tfstate.backup, these are related to the current terraform state, this is what Terraform compares against when you run plan, apply or destroy! Lets now look at storing this file remotely

Storing your terraform state file in a remote location (Azure Storage Account)
Create an Azure Storage Account & blob container (script here)
#!/bin/sh
# Create Resource Group
az group create -l uksouth -n thomasthornton-tfstate
# Create Storage Account
az storage account create -n thomasthorntontfstate -g thomasthornton-tfstate -l uksouth --sku Standard_LRS
# Create Storage Account blob
az storage container create --name tfstate --account-name thomasthorntontfstate

With a container created, we can now look at saving the remote state locally.
Update terraform backend to the below
terraform {
backend "azurerm" {
resource_group_name = "thomasthornton-tfstate"
storage_account_name = "thomasthorntontfstate"
container_name = "tfstate"
}
}
Now run terraform init with new property -migrate-state (you may be asked for storage account access key during this step)
thomasthorntoncloud@Thomass-MBP terraform % terraform init -migrate-state
Initializing the backend...
Backend configuration changed!
Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.
Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Using previously-installed hashicorp/azurerm v2.62.0
Checking the storage account, we can see the new terraform state file

Finishing up..
A lengthy blog post, but I hope you now have a firm understanding of “Getting started with Terraform on Azure”
Feel free to check out my other blog posts! Why not check out Deploy Terraform using Azure DevOps
Full search all references to Terraform on my blog
2 comments