Creating Your First Terraform Module for Azure
Why Use a Terraform Module?
If you’ve ever copied and pasted the same Terraform resources into more than one project, you’ll know how quickly things can get messy. Modules are your way out of that mess.
A module is basically a reusable bundle of Terraform config, like a function in code. You pass in some variables, and it gives you a consistent result every time. No more copy-paste chaos.
Here’s what makes modules worth it:
- Keeps your naming and tagging tidy
- Saves you from repeating yourself
- Helps you work faster without breaking stuff
If you’re working in a team, this becomes even more useful. Build a set of reusable pieces, like storage accounts, networks, or full environments, and suddenly everyone’s working with the same standards. No more “why is this resource named differently in Dev and Prod?” problems.
What We’re Building
We’ll keep this first example simple. We’re going to build a module that:
- Creates a Resource Group
- Deploys a Storage Account with a random suffix
We’ll be using:
- AzureRM provider
- Random provider
Step 1: Folder Structure
Here’s how to organise it:
project-root/
├── main.tf
├── modules/
│ └── storage/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
What each file does:
main.tf
in the root is your launcher. It sets up providers and uses the module.main.tf
inmodules/storage
holds your actual infrastructure code.variables.tf
lists the inputs the module expects.outputs.tf
defines what values you want back out of the module.
modules/storage/main.tf
resource "random_string" "suffix" {
length = 6
upper = false
special = false
}
resource "azurerm_resource_group" "example" {
name = var.resource_group_name
location = var.location
}
resource "azurerm_storage_account" "example" {
name = "elwst${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "LRS"
}
modules/storage/variables.tf
variable "resource_group_name" {
type = string
}
variable "location" {
type = string
default = "UK South"
}
modules/storage/outputs.tf
output "storage_account_name" {
value = azurerm_storage_account.example.name
}
Step 2: Using the Module
Now you can call the module from your root config like this:
provider "azurerm" {
features {}
}
provider "random" {}
module "storage" {
source = "./modules/storage"
resource_group_name = "example-rg"
location = "UK South"
}
Step 3: Init and Apply
In your terminal, run:
terraform init
terraform plan
terraform apply
You’ll get a new resource group and a storage account with a randomly generated name like elwstf3x9qd
. And if you want to use it in another environment? Just call the module again with different values.
Wrap Up
If you’re writing the same Terraform config more than once, modules are the fix. You don’t need to go wild with complexity. Just start small. Get a feel for the structure.
Then as you grow, your modules will grow with you. Add naming standards. Add tags. Wrap diagnostics and permissions into them. Before long, you’ve got a proper set of building blocks that your team can trust.
Keep your Terraform DRY (Don’t Repeat Yourself). By building reusable modules, you save yourself from rework and reduce the chance of inconsistency across environments. Your future self, and your team, will thank you for it.