Kickstarting Your Terraform Journey on Azure

Nicholas Foo
FAUN — Developer Community 🐾
9 min readDec 29, 2023

--

A Definitive Guide to Deploying Your First Azure Resource with Terraform

Table of Contents

What is Terraform? and why?
How is this done in Azure?
Let’s start writing your terraform configuration
Deploying your resources
Destroying your resources

If you search how to use Terraform for IaC (Infrastructure-as-Code) deployment, most of the resources are catered to AWS and not Azure. I hope this write-up can help those just starting to write their code for their Azure deployments.

First off, let me begin

What is Terraform? and why?

Hashicorp Terraform is an open-source IaC tool for provisioning and managing cloud infrastructure. It is one of the most popular cloud-agnostic tools out there to deploy your infrastructure. This means that by learning this tool, you use the same tool to provision your in AWS, Azure, GCP, etc.

It uses its language called HCL (HashiCorp Configuration Language) to codify your desired infrastructure in configuration files.

Why learn it when platforms like AWS, and Azure have such nice interfaces in their portals to deploy your infrastructure?

At the beginning of my cloud journey, I could not understand the motivation behind this. However, I started seeing its true benefits when I started deploying a simple 3-tier architecture

  1. IaC serves as your documentation as to how your architecture is/was configured.
  2. It also allows you to deploy and destroy your resources quickly. Especially during your learning phase, you will want to destroy your resources before your bill racks up!

How is this done in Azure?

Terraform uses providers to interact with Azure to deploy resources. For most of the deployments, the azurerm (Azure Resource Manager) provider is used.

The other commonly used provider is azuread (Azure AD), it is used when you need to manage the IAM and RBAC configurations. This is widely used in large enterprises when you need to control the permissions for multiple AD groups and their members. I will cover the security aspects in another topic.

Without further ado, let’s get started with our first deployment in Azure

Prerequisites

To follow this tutorial you will need:

  • An Azure Account — Get started here
  • Azure CLI — I personally use this to authenticate to Azure for my deployments
  • Have Terraform installed — follow the instructions here

Using Azure CLI

az login --user <username> --password <password>

To verify that you are at the right subscription

az account show

It should display the details of your subscription in this format

{
"environmentName": "AzureCloud",
"homeTenantId": "00000000-0000-0000-0000-000000000000",
"id": "00000000-0000-0000-0000-000000000000",
"isDefault": true,
"managedByTenants": [],
"name": "Your storage account name",
"state": "Enabled",
"tenantId": "00000000-0000-0000-0000-000000000000",
"user": {
"name": "yourName@contoso.com",
"type": "user"
}
}

id refers to your subscription ID.

I use this often to check that I am in the right subscription, especially when you have multiple subscriptions in your tenant. This is especially true for larger enterprises.

You can refer to this link for more details about Azure CLI

Let's start writing your terraform configuration!

First, create your directory to hold the configuration files. Then create your first .tf file. (from here I am writing shell commands in Linux)

touch main.tf

You can write your provider configuration in main.tf. However, it is good practice to separate your provider(s) into another file.

You can create as many .tf files as you want in the same folder. Terraform will read all the files together in a single deployment.

touch providers.tf

In the providers.tf, write your providers configuration

# Azure Provider source and version being used
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">=3.0.0"
}
}
}

# Configure the Microsoft Azure Provider
provider "azurerm" {
features {}
}

If you need to use azuread also, you will need to add another provider in the required_providers block. Let us keep it simple for now.

In the code

required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">=3.0.0"
}

you are telling Terraform that you want an azurerm version greater or equal to version 3.0.0. It is the same as the aws provider if you are familiar with it.

Do note features {} is mandatory for your provider block. You have to include it even though you are not using any of the features argument at the moment.

Going back to main.tf, we can create our first resource!

In Azure, all resources have to be associated with a Resource Group.

Let us first create the Resource Group. In your main.tf, write the following resource block

resource "azurerm_resource_group" "sample_rg" {
name = "myrg"
location = "eastus"
}

Deploying your resources

  1. Terraform init

Before you can start deploying, you need to run

terraform init

Terraform will then find all the necessary plugins for your deployment. You should see the following output

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/azurerm versions matching ">= 3.0.0"...
- Installing hashicorp/azurerm v3.85.0...
- Installed hashicorp/azurerm v3.85.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.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary

If you notice in the output, it says that “Terraform has created a lock file .terraform.lock.hcl”. This is one of the files that Terraform creates when you run terraform init.

As it says, include this in your control repository to ensure that Terraform can guarantee to make the same selections.

This is important, especially in an enterprise where you have to maintain multiple versions of the providers.

It is good practice to include this file when you push to your remote repository during your team collaboration.

2. Terraform plan

Remember in main.tf we had our resource block to create our resource group?

Now run terraform plan

terraform plan

and you should see this output

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.sample_rg will be created
+ resource "azurerm_resource_group" "sample_rg" {
+ id = (known after apply)
+ location = "eastus"
+ name = "myrg"
}

Plan: 1 to add, 0 to change, 0 to destroy.

There is an optional step to use theterraform validate command. This is to verify that your code syntax is valid. You can refer to more details here. However, I usually skip this step as terraform plan will also validate the syntax.

Simply deploying a resource group is not so meaningful. Let’s add a storage account to the resource group as well.

In your main.tf add the following code

resource "random_string" "storage_suffix" {
length = 4
special = false
upper = false
}

resource "azurerm_storage_account" "mystorage" {
name = "mystorage${random_string.storage_suffix.result}"
resource_group_name = azurerm_resource_group.sample_rg.name
location = "eastus"
account_tier = "Standard"
account_replication_type = "LRS"
}

I am generating a 4-character alphanumeric string which will be the suffix of my storage account name. Azure requires a unique global storage account name, similar to that of AWS S3 bucket.

You will need to re-initialize Terraform because you have added a hashicorp/random provider to generate your random string.

terraform init -upgrade 

After Terraform re-initialize, run terraform plan again.

You will see the following output:

$ terraform plan

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.sample_rg will be created
+ resource "azurerm_resource_group" "sample_rg" {
+ id = (known after apply)
+ location = "eastus"
+ name = "myrg"
}

# azurerm_storage_account.mystorage will be created
+ resource "azurerm_storage_account" "mystorage" {
+ access_tier = (known after apply)
+ account_kind = "StorageV2"
+ account_replication_type = "LRS"
+ account_tier = "Standard"
+ allow_nested_items_to_be_public = true
+ cross_tenant_replication_enabled = true
+ default_to_oauth_authentication = false
+ enable_https_traffic_only = true
+ id = (known after apply)
+ infrastructure_encryption_enabled = false
+ is_hns_enabled = false
+ large_file_share_enabled = (known after apply)
+ location = "eastus"
+ min_tls_version = "TLS1_2"
+ name = (known after apply)
+ 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_blob_internet_endpoint = (known after apply)
+ primary_blob_internet_host = (known after apply)
+ primary_blob_microsoft_endpoint = (known after apply)
+ primary_blob_microsoft_host = (known after apply)
+ primary_connection_string = (sensitive value)
+ primary_dfs_endpoint = (known after apply)
+ primary_dfs_host = (known after apply)
+ primary_dfs_internet_endpoint = (known after apply)
+ primary_dfs_internet_host = (known after apply)
+ primary_dfs_microsoft_endpoint = (known after apply)
+ primary_dfs_microsoft_host = (known after apply)
+ primary_file_endpoint = (known after apply)
+ primary_file_host = (known after apply)
+ primary_file_internet_endpoint = (known after apply)
+ primary_file_internet_host = (known after apply)
+ primary_file_microsoft_endpoint = (known after apply)
+ primary_file_microsoft_host = (known after apply)
+ primary_location = (known after apply)
+ primary_queue_endpoint = (known after apply)
+ primary_queue_host = (known after apply)
+ primary_queue_microsoft_endpoint = (known after apply)
+ primary_queue_microsoft_host = (known after apply)
+ primary_table_endpoint = (known after apply)
+ primary_table_host = (known after apply)
+ primary_table_microsoft_endpoint = (known after apply)
+ primary_table_microsoft_host = (known after apply)
+ primary_web_endpoint = (known after apply)
+ primary_web_host = (known after apply)
+ primary_web_internet_endpoint = (known after apply)
+ primary_web_internet_host = (known after apply)
+ primary_web_microsoft_endpoint = (known after apply)
+ primary_web_microsoft_host = (known after apply)
+ public_network_access_enabled = true
+ queue_encryption_key_type = "Service"
+ resource_group_name = "myrg"
+ secondary_access_key = (sensitive value)
+ secondary_blob_connection_string = (sensitive value)
+ secondary_blob_endpoint = (known after apply)
+ secondary_blob_host = (known after apply)
+ secondary_blob_internet_endpoint = (known after apply)
+ secondary_blob_internet_host = (known after apply)
+ secondary_blob_microsoft_endpoint = (known after apply)
+ secondary_blob_microsoft_host = (known after apply)
+ secondary_connection_string = (sensitive value)
+ secondary_dfs_endpoint = (known after apply)
+ secondary_dfs_host = (known after apply)
+ secondary_dfs_internet_endpoint = (known after apply)
+ secondary_dfs_internet_host = (known after apply)
+ secondary_dfs_microsoft_endpoint = (known after apply)
+ secondary_dfs_microsoft_host = (known after apply)
+ secondary_file_endpoint = (known after apply)
+ secondary_file_host = (known after apply)
+ secondary_file_internet_endpoint = (known after apply)
+ secondary_file_internet_host = (known after apply)
+ secondary_file_microsoft_endpoint = (known after apply)
+ secondary_file_microsoft_host = (known after apply)
+ secondary_location = (known after apply)
+ secondary_queue_endpoint = (known after apply)
+ secondary_queue_host = (known after apply)
+ secondary_queue_microsoft_endpoint = (known after apply)
+ secondary_queue_microsoft_host = (known after apply)
+ secondary_table_endpoint = (known after apply)
+ secondary_table_host = (known after apply)
+ secondary_table_microsoft_endpoint = (known after apply)
+ secondary_table_microsoft_host = (known after apply)
+ secondary_web_endpoint = (known after apply)
+ secondary_web_host = (known after apply)
+ secondary_web_internet_endpoint = (known after apply)
+ secondary_web_internet_host = (known after apply)
+ secondary_web_microsoft_endpoint = (known after apply)
+ secondary_web_microsoft_host = (known after apply)
+ sftp_enabled = false
+ shared_access_key_enabled = true
+ table_encryption_key_type = "Service"
}

# random_string.storage_suffix will be created
+ resource "random_string" "storage_suffix" {
+ id = (known after apply)
+ length = 4
+ lower = true
+ min_lower = 0
+ min_numeric = 0
+ min_special = 0
+ min_upper = 0
+ number = true
+ numeric = true
+ result = (known after apply)
+ special = false
+ upper = false
}

Plan: 3 to add, 0 to change, 0 to destroy.

We see now there are 3 resources to be created. Our resource group, our storage account which will be deployed inside our resource group, and the random string itself (yes! It is counted as 1 resource)

Everything looks good so we are ready to run terraform apply . When prompted, enter “yes”.

I prefer not to use the -auto-approve flag unless I am 100% sure I know what I am deploying.

You don't want to end up with a hefty bill because you deployed the wrong resource!

You should see a similar output

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

When you log into the Azure portal, you will see your deployed resources.

Destroying your resources

Clean up your resources by runningterraform destroy. Similar to the terraform apply , you can add the -auto-approve flag to destroy your resources without the prompt.

You can safely use the -auto-approve flag because you are just deprovisioning your infrastructure.

However, if you intend to partially destroy, for example, you just want to destroy the storage account and keep your resource group, just delete the resource block azurerm_storage_account and rerun terraform apply.

Conclusion

Hope this simple example helps you get started on your Terraform journey with Azure.

Photo by Mukuko Studio on Unsplash

👋 If you find this helpful, please click the clap 👏 button below a few times to show your support for the author 👇

🚀Join FAUN Developer Community & Get Similar Stories in your Inbox Each Week

--

--