Day 33: Mastering Terraform HCL Feature
May 9, 2025
•08:36 PM GMT+8

Today, we will dive into powerful features of HashiCorp Configuration Language (HCL) that improve the way we write and manage Terraform code. We will explore how to use count, for_each, lifecycle, and dynamic blocks to make your infrastructure more flexible, scalable, and maintainable.
Task
Clone the following repository and navigate to the correct directory named 05-HCL:
git clone https://github.com/git-adrianrubico/learn-terraform
cd learn-terraform/05-HCLThe main.tf file is configured to create a storage account using HCL features:
resource "azurerm_resource_group" "rg-hcl-example" {
name = "rg-hcl-example"
location = var.azregion
tags = {
environment = local.env
}
}
resource "azurerm_storage_account" "sa-example" {
name = "stgacsample${local.env}01"
resource_group_name = azurerm_resource_group.rg-hcl-example.name
location = azurerm_resource_group.rg-hcl-example.location
account_tier = "Standard"
account_replication_type = "LRS"
tags = {
environment = local.env
}
}Using count
The count parameter allows you to create multiple instances of a resource using a single block.
Here’s an example where two storage accounts are created with different names by using count = 2:
resource "azurerm_storage_account" "sa-example" {
count = 2
name = "stgacsample${local.env}${count.index}"
resource_group_name = azurerm_resource_group.rg-hcl-example.name
location = azurerm_resource_group.rg-hcl-example.location
account_tier = "Standard"
account_replication_type = "LRS"
tags = {
environment = local.env
}
}We also update the output.tf file to access one of the created instances:
output "sa_name" {
value = azurerm_storage_account.sa-example[0].name
}Here, we use [0] to reference the first instance from the list of storage accounts created using count. This is useful when you want to retrieve a specific instance from the list.

Picture below shows the two storage account created:

Using for_each
The for_each meta-argument is used when iterating over a map or set of strings. This allows you to deploy resources based on key-value pairs.
In the example below, we use for_each with a map of replication types:
each.keyis used to construct a unique name for each storage account.each.valuesets the appropriate replication type for each resource.
resource "azurerm_storage_account" "sa-example" {
for_each = {
lrs = "LRS"
grs = "GRS"
}
name = "stgacsample${local.env}${each.key}"
resource_group_name = azurerm_resource_group.rg-hcl-example.name
location = azurerm_resource_group.rg-hcl-example.location
account_tier = "Standard"
account_replication_type = each.value
tags = {
environment = local.env
}
}To output one of the created resources, we can convert the map into a list using the values() function:
output "sa_name" {
value = values(azurerm_storage_account.sa-example)[0].name
}Here, values(...) converts the map of storage accounts into a list, and [0] accesses the first resource from that list. This approach is especially useful when you only need to refer to one resource created with for_each.

Two storage account created for both LRS & GRS:

Terraform Lifecycle
Terraform lifecycle blocks give you control over how resources are treated during the apply or destroy phases.
resource "azurerm_resource_group" "rg-hcl-example" {
name = "rg-hcl-example"
location = var.azregion
tags = {
environment = local.env
}
lifecycle {
prevent_destroy = true
}
}
This configuration prevents accidental deletion of the resource group by requiring manual intervention to remove it.
💡Note: There are many
lifecyclemeta-arguments to explore that can help you regulate how Terraform handles your resources. Check out the full documentation here: Terraform Lifecycle Meta-Arguments
Using dynamic blocks
Terraform's dynamic block is useful when you want to create repeated nested blocks based on a list or map. This is especially handy for resources like Network Security Groups (NSGs) that may contain multiple security_rule blocks.
Below is an example where we define two NSG rules dynamically using a local variable:
main.tf
resource "azurerm_network_security_group" "nsg-example" {
name = "nsg-example"
location = azurerm_resource_group.rg-hcl-example.location
resource_group_name = azurerm_resource_group.rg-hcl-example.name
dynamic "security_rule" {
for_each = local.nsgrules
content {
name = security_rule.value.name
priority = security_rule.value.priority
direction = security_rule.value.direction
access = security_rule.value.access
protocol = security_rule.value.protocol
source_port_range = security_rule.value.source_port_range
destination_port_range = security_rule.value.destination_port_range
source_address_prefix = security_rule.value.source_address_prefix
destination_address_prefix = security_rule.value.destination_address_prefix
}
}
}locals.tf
locals {
env = "dev"
nsgrules = [{
name = "HTTP"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
},
{
name = "HTTPS"
priority = 101
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}]
}output.tf
output "nsg_name" {
value = azurerm_network_security_group.nsg-example.name
}- We define a local variable
local.nsgrulesthat holds a list of security rules. - The
dynamic "security_rule"block iterates over each rule inlocal.nsgrulesusingfor_each. - Inside the
content {}block, we assign values to each required NSG rule attribute usingsecurity_rule.value.
This pattern is extremely powerful for managing multiple similar blocks without duplicating code, especially in environments where rules change frequently or are sourced from external data.

Verify Network Security Group rules:

Conclusion
We discussed how HCL features such as for_each, lifecycle, and dynamic blocks simplify and scale Terraform code. In addition to improving the way you define and manage infrastructure, these tools help you follow best practices at the same time.
We will discuss Terraform drift in the next blog, along with how to detect and manage changes made outside of Terraform.
Day 33: Mastering Terraform HCL Feature
Master Terraform HCL features including count, for_each, lifecycle, and dynamic blocks to build scalable and reusable infrastructure code.
For the passion of automated cloud solutions.
Subscribe to get the latest posts. I mostly write about Backend (Python/Bash), DevOps and Linux.