What are Terraform Modules and how to use them

Let's take a look at what Terraform modules are and how to use them.

What are Terraform Modules and how to use them
What are Terraform Modules and how to use them

Infrastructure as Code (IaC) can help you manage your infrastructure, however your code base can quickly become complex as your infrastructure grows. 

Terraform modules can help bring simplicity, consistency and reusability to your deployment.  Whether you're orchestrating a single resource or a sprawling multi-region architecture, Terraform modules let you package configurations into tidy, reusable components. In this post, we’ll explore what Terraform modules are, why they’re invaluable, and how they can transform the way you build and manage your infrastructure.  

What is a Terraform Module?

A Terraform module is a container for multiple resources that are used together to achieve a specific goal. It simplifies the management and reuse of infrastructure-as-code (IaC) by packaging related configurations into a single, reusable entity. 

For example, instead of defining individual Azure resources like a virtual network, subnets, and network security groups repeatedly, you can bundle them into a single module. This approach streamlines deployments and ensures consistency across environments.

Types of Terraform Modules

Root Modules

A root module is the starting point of any Terraform configuration. It comprises all the files in your working directory, such as main.tf, variables.tf, and outputs.tf. When you run Terraform commands like terraform apply, the root module is what Terraform executes.

Child Modules

Child modules are reusable modules called by the root module or another child module. You define these in separate directories or reference external sources, such as public module registries or private Git repositories.

For instance, you might use a public Azure Resource Group module from the Terraform Registry to create resource groups consistently across different projects.

Public Modules 

Available on the Terraform Registry, these are pre-built modules created by the community or providers like Azure.

Private Modules

Custom-built for internal use, private modules are often stored in a private Git repository or an enterprise registry. These are tailored to your organisation’s specific needs.

When should you use Terraform Modules?

It depends on your IaC journey, your organisation policies but ultimately the answer is always.

Terraform modules help you manage multiple environments and provide consistent infrastructure.  As well as helping with reusability.

There are times when your Terraform code can become large, so defining modules can help keep code straightforward. 

When I was learning Terraform I didn’t use modules until I understood the basic concepts of the language and then started to incorporate modules in my code. 

How to use Terraform Modules

Create a directory for your module and add the necessary .tf files. For example, a module for an Azure storage account might include:

  • main.tf: Defines the storage account resource.
  • variables.tf: Defines input variables like the resource group name and location.
  • outputs.tf: Specifies outputs such as the storage account name and endpoint.
Terraform module structure
Terraform module structure

First you can define the resources in main.tf:

resource "azurerm_storage_account" "example" {
  name                     = var.storage_account_name
  resource_group_name      = var.resource_group_name
  location                 = var.location
  account_tier             = var.account_tier
  account_replication_type = var.account_replication_type
  tags = var.tags
}

Then set up the variables within the variables.tf file

variable "storage_account_name" {
  description = "The name of the storage account."
  type        = string
}
variable "resource_group_name" {
  description = "The name of the resource group."
  type        = string
}
variable "location" {
  description = "The Azure region where the storage account will be created."
  type        = string
}
variable "account_tier" {
  description = "The performance tier of the storage account (Standard or Premium)."
  type        = string
  default     = "Standard"
}
variable "account_replication_type" {
  description = "The replication type of the storage account (LRS, GRS, RAGRS, ZRS)."
  type        = string
  default     = "LRS"
}
variable "tags" {
  description = "A mapping of tags to assign to the resource."
  type        = map(string)
  default     = {}
}

And then lastly within the outputs.tf file define the relevant outputs:

output "storage_account_name" {
  value = azurerm_storage_account.example.name
}
output "primary_blob_endpoint" {
  value = azurerm_storage_account.example.primary_blob_endpoint
}

You can then call the module within your main Terraform configuration file:

module "storage_account" {
  source = "./modules/storage-account"
  storage_account_name = "mystorageaccount"
  resource_group_name  = "my-resource-group"
  location             = "East US"
  account_tier         = "Standard"
  account_replication_type = "LRS"
  tags                 = {
    environment = "dev"
    project     = "example-project"
  }
}

Remember to customise the deployment by passing in variables that relate to your environment and deployment. 

💡
Be sure to keep an eye out for a tutorial I will be releasing on creating and using Terraform modules soon!

Versioning Terraform modules

When calling a module you can specify a version argument.  The version argument only works with modules that are stored on a Terraform module registry.  It won’t work for modules that are located in local file systems or git repositories. 

For example if we were to call the Azure Verified Modules (AVM) to provision Azure Stack HCI use can specify to use version 0.12.0:

module "avm-res-azurestackhci-cluster" {
  source  = "Azure/avm-res-azurestackhci-cluster/azurerm"
  version = "0.12.0"
}

When we run terraform init Terraform will look for version 0.12.0 of the module and download it.

You can also specify a range of versions and Terraform will use the newest version of the module that matches within the constraint you have given. 

What problems do Terraform Modules solve?

Using modules solves quite a few problems in your code. 

As your infrastructure managed by Terraform grows so will your code, using modules can help save you from copy-pasting an entire stack of code to create a single instance of something. 

Good code is readable code, which is where modules can help with.  A modular approach to code keeps it clean, simple and readable. 

Modules allow teams to work in parallel by providing isolated, reusable components that can be integrated seamlessly.

For example, using a module to manage Azure Kubernetes Service (AKS) clusters ensures that networking, node pools, and monitoring configurations follow best practices, regardless of who is deploying them.

Conclusion

Terraform modules are an essential tool for anyone managing infrastructure with Terraform. By organizing your configurations into reusable, consistent components, they simplify your code, improve scalability, and enhance collaboration across teams. Whether you're just starting your Terraform journey or are already managing complex multi-region architectures, embracing modules can dramatically improve your infrastructure-as-code practices.

If you're not using Terraform modules yet, now is the perfect time to start! Experiment with creating a module for a small project or leverage public modules from the Terraform Registry to see how they can streamline your deployments.