Terraform Modules
In Terraform, modules help structure our configuration by preventing it from being monolithic, meaning everything in a single file. Instead, we organize different parts of the configuration into logical groups and package them within folders, each representing a module.
For example, a logical grouping of resources could include everything needed to create an EC2 instance. This module would contain resources such as retrieving the most recent image and defining the security group, since the EC2 instance requires both. This grouping of resources can be structured as a reusable webserver module.
When deploying infrastructure with multiple instances across different regions or availability zones, we can reuse the same webserver module in various locations. One of the key benefits of defining a module is that we can pass in parameters dynamically, allowing us to customize values from the outside. Additionally, we can access the module’s output, meaning we can retrieve objects and attributes after the resources are created.
If you’re familiar with programming, you can think of modules as functions. Just like a function, a module is defined once and can be reused multiple times within the configuration. We can pass parameters to it, decide what values to include, and retrieve outputs, making infrastructure management more modular and flexible
Modules help keep Terraform configurations clean and organized by logically grouping related resources, making them easier to manage. However, a module should be a proper abstraction, meaning it should group multiple related resources rather than just a single resource, to avoid unnecessary complexity.
For example, a web server module could include not just the EC2 instance but also its security group, key pair, and attached volumes. Similarly, a VPC module could bundle subnets, an internet gateway, and a route table.
While we can create our own modules, many commonly used ones—such as VPC or IAM modules—are already available from Terraform, companies, or individual developers, allowing for easier reuse and standardization.
Terraform Registry provides a collection of modules for various technologies, not just AWS. It allows users to browse and reuse pre-built modules instead of creating them from scratch. For example, AWS and Google Cloud modules group related resources together for easy deployment.
Each module includes documentation detailing the resources it creates and the configurable parameters, listed under the Inputs tab. Users only need to provide the necessary values while others remain optional. The Outputs tab shows the resulting resources and attributes, such as VPC and subnet IDs, which can be referenced in other modules.
Modules may also have dependencies, but they automatically install required providers, like AWS, during terraform init, eliminating the need to define them separately in the main configuration.
We can create our own modules but also reuse the ones that AWS or Terraform created and reuse them to make it much easier.
Modularise our project
Best practise and a common way of writing terraform configuration file to separate the outputs into output dot TF file variables into variable dot TF file as well as providers into provider dot TF file. So basically, end up with just a resource definitions inside your main dot TF.
Terraform is that we don’t have to link these files to each other. We don’t reference the variables TF and outputs TF from the main dot TF file because Terraform knows that these files belong together, and it grabs everything and interprets everything and links them together. So, we don’t have to cross reference them like we do in some programming languages.
Create a Module:
create now a folder called modules. And inside the modules we are going to create folders for the actual modules so we can have for example the webserver module and then we can have another module and let’s call it subnet.
So now we have prepared folders for two modules and each module will have its own main.tf, outputs.tf, providers.tf and variables.tf.

When creating modules and their configurations, we can reference them directly from main.tf, which serves as the entry point for our root module. Inside this root module, we define and reference child modules. A module should typically group at least 3-4 resources to be meaningful. In our case, the subnet module will encapsulate the entire networking configuration by including the subnet itself, the internet gateway, and the default route table along with its gateway association. These resources will be moved into the main.tf of the subnet module.

Need to change all the root module references to variables.
aws_internet_gateway is of a resource that exists in the same module, so we don’t have to replace it through a variable because we have that resource available in the same context. So, we can leave this as is.
Now we must define all those variables inside that module in the variables.tf.
variable vpc_id {}
variable subnet_cidr_block {}
variable avail_zone {}
variable env_prefix {}
variable default_route_table_id {}
Use the Module:
we have the subnet module configuration and variables defined, how we use that module? how do we reference it from another configuration file and the way we do that is using a module keyword, so we’re going to write module and then we’re going to call this module something that we want. So, let’s actually call it myapp-subnet in main.tf
we’re going to need a couple of attributes. The first one is the source of the module. So where is this module actually living , and the source in our case is just a location or path to the folder of our module and that is modules/subnet.
we must pass in all those variables that we defined in subnet module So the values to those variables need to be provided when we are referring to a module .

# Import my own subnet module, give it a name could be any.
module “myapp-subnet”{
source = “./modules/subnet”
vpc_id = aws_vpc.terraform-vpc-demo.id
subnet_cidr_block = var.subnet_cidr_block
avail_zone = var.avail_zone
env_prefix = var.env_prefix
default_route_table_id = aws_vpc.terraform-vpc-demo.default_route_table_id
}
If we are referencing a module using a relative path. So basically relative to the main dot file, then we have to specify ./modules/subnet
Module Output
Resource that we created by a module , how it will be used or reference in another module? So basically, we want to access the subnet ID of the subnet resource that is being created by subnet module and the way we do that is first, we need to do we need to basically output the subnet object.
output “subnet” {
value = aws_subnet.terraform-subnet-1
}
The syntax is going to be module.<name of the module>.<output name> this will basically give us the object. So, this is how you can access the attributes or objects created from resources defined in another module.
# Import webserver module
module “myapp-webserver” {
source = “./modules/webserver”
vpc_id = aws_vpc.terraform-vpc-demo.id
my_ip = var.my_ip
env_prefix = var.env_prefix
public_key_location = file(var.public_key_location)
instance_type = var.instance_type
subnet_id = module.myapp-subnet.subnet.id
avail_zone = var.avail_zone
}
Same way we can create myapp-webserver module and complete root main.tf .