Terraform Modules: Reusable Infrastructure Blueprints
A Terraform module is a reusable container for related resources, letting you stamp out infrastructure from one blueprint instead of copying HCL. Teams share VPC patterns or tagging standards with them.
WHY IT EXISTS: Before modules, every Terraform project was a flat directory of resources. If you needed three identical environments, you copied folders and ran sed replacements, which guaranteed drift. Modules exist to package infrastructure logic so you can declare intent once and instantiate it many times with different inputs, the same way a function lets you call the same code with different arguments.
THE MENTAL MODEL: Think of a module as a blueprint for a machine. The blueprint defines every gear and wire, but it is not a machine until you build it. In Terraform, calling a module is like ordering a machine from that blueprint and passing a spec sheet of custom options. The module returns the built machine's serial numbers and IP addresses as outputs. This separates what you are building from how you configure it.
HOW IT WORKS: A module is simply a directory containing Terraform configuration files. It declares input variables, creates resources using those variables, and exposes outputs. When another configuration invokes the module with a module block, Terraform copies the module's code into its evaluation graph, binds the supplied arguments to the variables, and tracks the resulting resources as a single logical unit. State paths reflect the module call hierarchy, so a module named web inside an environment called prod creates state keys like module.web. You can source modules from local paths, versioned Git repositories, or the Terraform Registry.
WHEN TO USE IT: Use modules when you have a pattern that repeats across teams or environments. A standard Kubernetes cluster, a VPC with private subnets and NAT gateways, or an S3 bucket with encryption and lifecycle policies are all prime candidates. Modules are also the right place to encode organizational guardrails, such as mandatory tagging or backup retention, because they let platform teams publish vetted building blocks that application teams consume without understanding every underlying resource.
WHEN NOT TO USE IT: Do not wrap a single resource in a module unless you are adding real policy logic. A module that only passes a name into an aws_instance and returns its ID adds indirection with no benefit. Avoid deep nesting: if a module calls a module that calls another module, debugging a plan requires jumping through multiple directories to find where a variable is actually used. Keep the graph shallow and the interfaces explicit.
ONE CANONICAL EXAMPLE: A platform team publishes a vpc module that accepts cidr_block and az_count. It internally creates the VPC, Internet gateway, private and public subnets, route tables, and NAT gateways. An application team calls this module twice: once with 10.0.0.0/16 for production and once with 10.1.0.0/16 for staging. The platform team later adds VPC flow logs by updating the module, and both environments pick up the change on the next apply without the application teams touching their code.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.