Let's think about it this way for a second
If you manually copy-paste the server + database + network setup for all three environments (dev/staging/prod), you end up maintaining nearly identical code in three places, which is error-prone — find one bug and you have to fix it in all three spots. A module lets you write that code once, like a function, and call it with `module "name" { source = "./path"; input = value }` — dev, staging, and prod can all call the same module while passing different values for things like instance_size and environment name. A module's inputs are defined with variable blocks, and its outputs with output blocks.
Let's connect this to a real scenario
You can put `main.tf` (resources), `variables.tf` (inputs), and `outputs.tf` (outputs) inside a `modules/web-app/` folder, then in the root config call it twice with `module "dev" { source = "./modules/web-app"; instance_type = "t3.micro" }` and `module "prod" { source = "./modules/web-app"; instance_type = "t3.large" }` — no need to write the code inside the module twice.
Let's look at it together
# modules/web-app/main.tf
variable "instance_type" {
type = string
}
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = var.instance_type
}
output "public_ip" {
value = aws_instance.web.public_ip
}
# root main.tf
module "dev" {
source = "./modules/web-app"
instance_type = "t3.micro"
}
module "prod" {
source = "./modules/web-app"
instance_type = "t3.large"
}$ terraform apply
module.dev.aws_instance.web: Creating...
module.prod.aws_instance.web: Creating...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.5-minute try-it
Create a `modules/web-app/` folder (split into variables.tf, main.tf, outputs.tf), then call it twice from the root config, passing a different instance_type each time.
A quick word of caution
Changing a module and applying it can affect every environment that calls that module — test in staging before changing a production module.