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-HCL
The 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.key
is used to construct a unique name for each storage account. -
each.value
sets 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
lifecycle
meta-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.nsgrules
that holds a list of security rules. -
The
dynamic "security_rule"
block iterates over each rule inlocal.nsgrules
usingfor_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.