ハブ記事: Terraform Modules: Complete Guide →
Hub article covering the full picture of Terraform modules: design, distribution, and operations
With Terraform, the same end state can have very different operational cost and blast radius depending on how you slice up the design. This article gives concrete guidance on when to reach for single-purpose modules, composite modules, and the layered model.
The discussion is grounded in stable, general behavior from HashiCorp's official documentation. Since specific features vary across clouds and versions, pattern selection here prioritizes maintainability and a clear apply order.
A single-purpose module focuses on one clear responsibility (e.g., VPC, subnet, security group) and keeps inputs and outputs minimal. A composite module bundles several single-purpose modules together, embedding guardrails and standard settings to boost reusability. Layers use the root module (stack) as a boundary to enforce apply order and separation of concerns (e.g., Bootstrap, Network, Platform, Application).
In practice, slicing too finely into single-purpose modules increases the burden on consumers, while leaning too hard on a giant composite or a single layer widens the blast radius when things go wrong. The right balance depends on your organization's change frequency and operating model.
| Aspect | Single-purpose module | Composite module | Layer (root module) |
|---|---|---|---|
| Purpose | Minimize responsibility to maximize reuse | Combine single-purpose modules with embedded standards | Clarify apply order and boundaries |
| Scope of responsibility | A resource type or small grouping | Per workload or per domain | Infrastructure tiers (Network/Platform/App, etc.) |
| Input/output granularity | Few inputs, strict types, stable outputs | Bundled object inputs, representative outputs | Only the minimal outputs needed by other layers |
| Dependency handling | Explicitly delegated to the parent | Order among child modules is coordinated internally | Loosely coupled to other layers via outputs |
| When to apply | Componentization in isolation, testability-first | Rapid rollout of a standard configuration | Environment separation, responsibility split, parallel deployments |
| Blast radius on failure | Small (easy to roll back) | Medium (scoped to the bundle) | Large within the layer, but unlikely to spill beyond the boundary |
Single-purpose modules use strict input types, plus defaults and validations, to guard against consumer mistakes. Outputs are pared down to the minimum that downstream callers actually need, which reduces the surface area for breaking changes.
Keep variables as scalars or small objects — avoid catch-all map variables that accept anything. When outputs contain sensitive data, mark them sensitive so they don't get inadvertently exposed in plan output at higher levels.
A composite module locks in the wiring diagram between single-purpose modules and bakes in organizational standards like tagging, encryption, and logging. Consumers only need to supply a small set of inputs to get a standards-compliant stack.
When passing aliased providers to child modules, you must propagate them explicitly via the providers map. If you use for_each to spawn multiple children, use stable keys (e.g., logical names) to keep diffs quiet.
Skeleton of a composite module (network_stack)
variable "name" { type = string }
variable "region" { type = string }
variable "network" {
type = object({
cidr_block = string,
subnets = map(object({ az = string, cidr_block = string }))
})
}
variable "security_rules" { type = list(object({
description = string,
protocol = string,
from_port = number,
to_port = number,
cidr_blocks = list(string)
})) }
locals {
default_tags = { "managed-by" = "terraform", "stack" = var.name }
}
provider "aws" { region = var.region }
module "vpc" {
source = "../modules/vpc"
name = var.name
cidr_block = var.network.cidr_block
tags = local.default_tags
}
module "subnet" {
for_each = var.network.subnets
source = "../modules/subnet"
vpc_id = module.vpc.id
az = each.value.az
cidr_block = each.value.cidr_block
tags = local.default_tags
}
module "sg" {
source = "../modules/security_group"
vpc_id = module.vpc.id
name = "${var.name}-base"
rules = var.security_rules
tags = local.default_tags
}
output "vpc_id" { value = module.vpc.id }
output "subnet_ids" { value = [for s in module.subnet : s.id] }
output "security_group_id" { value = module.sg.id }Layers split at the root-module level to make the apply order explicit. A typical setup is 4 layers: Bootstrap (backend, IAM least privilege), Network (VPC, etc.), Platform (EKS/ECS/Compute foundation), and Application (services). Each layer owns its own state, and inter-layer coordination flows through outputs.
Keep inter-layer dependencies loosely coupled via outputs. When you really do need a value from another layer, read it with data.terraform_remote_state — but watch out for circular dependencies and over-referencing. Run environments (dev/stg/prod) in parallel via directory separation and separate backends, with CI running plan/apply layer-by-layer in order.
Modules should follow semantic versioning, grouping breaking changes (renamed inputs, removed outputs, logical changes that force resource replacement) into major version bumps. Always ship a changelog and migration steps alongside.
On the consumer side, pin Terraform itself and providers via required_version and required_providers, and constrain registry-distributed modules via version. When a child module requires specific provider configuration (e.g., aliases), document how to pass it through explicitly via the providers map.
The Pro exam frequently tests separation of concerns, dependency handling, provider propagation, choosing between for_each and count, and handling sensitive data. Answers that favor state separation at module boundaries and minimal outputs tend to be correct.
Common anti-patterns include catch-all map variables, monolithic modules applied in one shot, excessive cross-layer remote_state references, for_each on volatile keys, and failing to propagate provider aliases.
Pro
問題 1
You need to enforce organizational standards for tagging, encryption, and network design across multiple environments (dev/stg/prod), while absorbing per-app differences through a minimal set of inputs. You also want to keep the blast radius small on failure and make the apply order explicit. Which design is most appropriate?
正解: A
Composites with embedded standards reduce input burden and drift, and layering makes apply order and state boundaries explicit — matching the requirements. A giant monolith widens the blast radius, wiring single-purpose modules directly is high-input and prone to drift, and data sources alone can neither create nor enforce standards.
Should I separate environments by directory or by workspace?
For production use, the default should be directory separation with separate backends (state), and workspaces should be used as a supplement when needed. This makes it easier to keep permission boundaries, locks, and lifecycles independent per environment.
Should I pass values between layers via variables or terraform_remote_state?
Pass values directly via variables and outputs between modules within the same layer, and only use terraform_remote_state to reference a minimal set of outputs when crossing layer boundaries. Avoid circular dependencies and excessive references — keep the boundaries loosely coupled.
How should modules handle provider versions and aliases?
Declare provider version ranges explicitly in required_providers at the root, and define aliases (e.g., for separate regions) as needed. Pass aliases to child modules explicitly via the providers argument on the module block. Document the expected provider configuration in the module's README so compatibility is preserved.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...