How it works
The explanation, with examples you can run.
A small module
# modules/service/main.tfvariable "name" { type = string }variable "env" { type = string } resource "random_pet" "suffix" { prefix = "${var.env}-${var.name}"} resource "local_file" "manifest" { filename = "${path.root}/out/${random_pet.suffix.id}.txt" content = "name=${random_pet.suffix.id}\n"} output "id" { value = random_pet.suffix.id }# main.tf (root)locals { services = toset(["api", "worker"])} module "service" { source = "./modules/service" for_each = local.services name = each.key env = "dev"} output "ids" { value = { for k, m in module.service : k => m.id }}Applied with Terraform 1.16.6:
Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: ids = { "api" = "dev-api-literate-dingo" "worker" = "dev-worker-workable-duck"}and the state addresses include the module path and key:
module.service["api"].local_file.manifestmodule.service["api"].random_pet.suffixmodule.service["worker"].local_file.manifestmodule.service["worker"].random_pet.suffixSources and versions
module "vpc" { source = "terraform-aws-modules/vpc/aws" # public registry version = "~> 6.0"} module "alarms" { source = "git::https://github.com/example/tf-modules.git//alarms?ref=v2.3.1"}Local paths need no version. Registry modules take version; Git sources take a ref (a tag or commit). terraform init downloads them into .terraform/modules. Read third-party modules before using them: they run with your credentials and decide what your infrastructure looks like.
Design guidelines
- One purpose per module, with a few meaningful inputs and sensible defaults. A module that exposes every argument of every resource is not an abstraction.
- Composition over nesting. Let the root module connect modules by passing outputs to inputs (
vpc_id = module.network.vpc_id), rather than modules that create their own networks. Dependencies stay visible and each piece stays testable. - No provider blocks in reusable modules: declare
required_providersand receive configurations from the caller (providers = { aws = aws.us }). - Stable addresses: renaming resources inside a module affects every caller. Add
movedblocks when you refactor, and release a new version. - Document and test: variable descriptions, an example directory, and
terraform testfiles (see the testing topic) make a module trustworthy.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.