Root and Child Modules

Scenario: Three separate teams each copy-paste the same network setup into their own configuration, each team fixes the same underlying bug slightly differently, and a year later nobody can upgrade all three consistently.

You've actually been working with modules since the very first section of this track, whether you noticed or not: every Terraform directory is a module, and specifically the directory you run Terraform commands from is called the root module. What's new here is the idea of a child module - a separate, self-contained module that the root (or another module) calls into, using a module block, so its resources can be reused instead of copy-pasted, which is exactly the fix for the scenario above.

A module block's source argument says where to find that child module's code - a local relative path (as below), a entry in the Terraform Registry, a VCS repository, or several other supported location types. Values flow into the child through its own variable blocks (the same input-contract mechanism from Module 4, just applied to a module instead of a single resource), and results flow back out through the child's output blocks, which the caller reads as module.<name>.<output>. Resources created inside a child module get a correspondingly nested address, like module.network.fakecloud_network.main - useful to recognize when you're reading terraform state list output on a configuration that uses modules.

Analogy: A module is a manufactured product assembly with one clearly documented connector on the outside. A consumer plugs into that connector; they shouldn't need to pry the case open and solder a wire directly onto an internal component just to get the behavior they need - if they do, the module's public interface (its variables and outputs) wasn't designed generously enough.

A worked configuration

module "service" {
  source = "./modules/service"
  name   = "payments"
}
output "service_endpoint" { value = module.service.endpoint }

Drawing a good module boundary is mostly about lifecycle and ownership, not line count. A module that tries to bundle every resource type a whole company might ever need becomes impossible to compose flexibly - every caller ends up fighting its assumptions. On the other end, a module that wraps a single resource with no added defaults, validation, or opinion often isn't earning its own complexity; a plain resource block would have been just as clear. Aim for a module that encapsulates one genuinely useful, cohesive capability, ships safe sensible defaults, and exposes a deliberately small, well-documented contract.

Common mistakes in production

Goal: Treat the plan as a change contract: understand every create, update, replacement, and destroy before approving it. Reinforce this lesson in the tf-variables-outputs-modules Terraform lab. Open /labs/terraform and choose slug tf-variables-outputs-modules; the lab runs real Terraform against the offline FakeCloud provider.