Welcome back to the Terraform series on AzureIs.Fun! If you’ve been following along, you’ve already taken the first steps into the world of Infrastructure as Code (IaC) with Terraform. Today we are looking at Terraform Modules. A great way to making reusable, scalable and maintenable code.
The remainin articles in the series:
- Getting Started with Terraform on Azure
- Transition from ARM Templates to Terraform with AI
- Terraform Configuration Essentials: File Types, State Management, and Provider Selection
- Writing Your First Azure Terraform Configuration
- Modules in Terraform (You are here)
- Deploy Azure Monitor with Terraform
- Advanced Terraform Techniques and Best Practices (TBD)
- Integrating Terraform with Azure DevOps (TBD)
- Terraform Associate Certification Study Guide and Tips (TBD)
As you progress in your Terraform journey, you’ll quickly realize that writing configurations from scratch for every deployment isn’t scalable. That’s where Terraform modules come in.
Modules allow you to reuse, organize, and manage infrastructure code efficiently. Whether you’re working solo or in a team, using modules will improve consistency, maintainability, and scalability.
In this article, we’ll break down what Terraform modules are, how to create your own, and how to use external modules to simplify infrastructure management in Azure.
What Are Terraform Modules?
A Terraform module is simply a collection of .tf files that encapsulate a piece of infrastructure. A module can be as simple as a single resource or as complex as an entire application deployment.
At its core, a module consists of:
- main.tf – Defines the resources.
- variables.tf – Defines input variables for flexibility.
- outputs.tf – Exposes key values from the module.
You can think of modules like functions in programming – you define them once and reuse them multiple times.
Why Use Terraform Modules?
- Reusability – Define once, use everywhere.
- Maintainability – Update infrastructure in one place.
- Consistency – Reduce errors with standardized deployments.
- Collaboration – Team members can share and use modules.
Let’s say you often deploy Azure Storage Accounts. Instead of copying and pasting Terraform code, you can modularize it.
Creating Your First Terraform Module
Create a new folder, e.g., modules/storage_account
, and add three files:
main.tf – Defines the actual infrastructure resources, such as an Azure Storage Account, using Terraform configuration:
1
2
3
4
5
6
7
8
9
modules/storage_account/main.tf
resource "azurerm_storage_account" "this" {
name = var.storage_account_name
resource_group_name = var.resource_group_name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
}
variables.tf – Declares input variables to make the module flexible and reusable with different values:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
modules/storage_account/variables.tf
variable "storage_account_name" {
description = "The name of the Azure Storage Account"
type = string
}
variable "resource_group_name" {
description = "The name of the Azure Resource Group"
type = string
}
variable "location" {
description = "The Azure Region"
type = string
default = "East US"
}
outputs.tf – Exposes key information from the module, such as resource IDs, so they can be referenced outside the module:
1
2
3
4
5
6
modules/storage_account/outputs.tf
output "storage_account_id" {
value = azurerm_storage_account.this.id
}
Simple as that. And we have our first Terraform module.
Using the Module in Your Terraform Configuration
Now, let’s use the module in a root Terraform configuration.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
main.tf
provider "azurerm" {
features {}
}
# Creating an Azure Resource Group to organize related resources. You can also use an existing one if you already have it.
resource "azurerm_resource_group" "rg" {
name = "my-resource-group" # Name of the resource group
location = "East US" # Azure region where the resource group will be created
}
# Calling the Terraform module for storage account
module "storage" {
source = "./modules/storage_account" # Path to the storage account module
# Passing required input variables to the module
storage_account_name = "azureisfunstorageacct123" # Name of the storage account (must be globally unique in Azure)
resource_group_name = azurerm_resource_group.rg.name # Referencing the created resource group
location = azurerm_resource_group.rg.location # Ensuring storage account is in the same region as the resource group
}
# Output to display the created storage account ID
output "storage_id" {
value = module.storage.storage_account_id # Fetching the output from the module
}
Running this will deploy a storage account using a module, making it reusable for any future deployments!
Using External Terraform Modules
Instead of creating modules from scratch, you can use public Terraform modules from Terraform Registry.
For example, to deploy a virtual network, you can use the official module:
1
2
3
4
5
6
7
module "vnet" {
source = "Azure/network/azurerm"
version = "5.0.0"
resource_group_name = "my-resource-group"
location = "East US"
address_space = ["10.0.0.0/16"]
}
This saves time and ensures best practices are followed. Full list of modules is available on the Terraform Registry website.
Best Practices for Terraform Modules
Keep Modules Small and Focused
Instead of a mega-module for everything, split into network, storage, compute, etc.
Use Meaningful Variable and Output Names
Bad: var.name
Good: var.storage_account_name
Version Control Your Modules
Store modules in GitHub or Terraform Registry.
1
2
3
module "storage" {
source = "git::https://github.com/myorg/terraform-modules.git//storage"
}
Use terraform fmt and terraform validate
Ensure module quality by using built in tools to format and validate your code:
1
2
terraform fmt
terraform validate
Transitioning from Monolithic Terraform to Modules
If you’ve been managing large Terraform files, here’s how to modularize:
Before: A Monolithic Configuration
1
2
3
4
5
6
7
8
9
10
11
12
resource "azurerm_resource_group" "rg" {
name = "my-resource-group"
location = "East US"
}
resource "azurerm_storage_account" "storage" {
name = "mystorageacct123"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
After: Using a Module
1
2
3
4
5
6
module "storage" {
source = "./modules/storage_account"
storage_account_name = "mystorageacct123"
resource_group_name = module.rg.name
location = module.rg.location
}
By moving logic into modules, your code becomes cleaner, more reusable, and easier to manage.
Advanced Module Concepts
Nested Modules
You can call a module inside another module, creating a hierarchy.
Dynamic Modules with for_each and count
Instead of hardcoding values, use loops:
1
2
3
4
5
6
7
8
module "storage_accounts" {
source = "./modules/storage_account"
for_each = toset(["storage1", "storage2", "storage3"])
storage_account_name = each.key
resource_group_name = "my-rg"
location = "East US"
}
This will deploy three storage accounts dynamically.
I hope this was useful as a first step in starting with Terraform modules. In case you have any questions, feel free to reach out.
Vukasin Terzic