Check that the repository follows the standard module structure: Terraform files in the root, a README, documented inputs and outputs, examples that use the external source address, and clear signals about which nested modules are public. Structure problems often hide interface problems.
The standard layout
HashiCorp's structure page says only the root module is required, and that Terraform files must sit in the repository's root directory. It then recommends:
- README.md describing the module and its purpose.
- LICENSE, even a non-open-source one, so organizations can adopt the module.
- main.tf as the primary entrypoint, where nested module calls go.
- variables.tf and outputs.tf for declarations.
- A one or two sentence description on every variable and output.
A minimal module is README.md, main.tf, variables.tf and outputs.tf. A complete one adds LICENSE, a modules/ directory and an examples/ directory.
Review checklist
| Check | Why it matters |
|---|---|
| Terraform files in repository root | Required for the module to load |
| Every variable has a description | Callers understand the input before using it |
| Every output has a description | Outputs are the interface callers depend on |
| README explains purpose | Reviewers can judge whether the change fits |
| LICENSE present | Other teams and organizations can adopt it |
| Examples exist | Show intended use and get copied by callers |
| Nested modules documented | Signals which are public |
Nested modules and examples
Nested modules live under modules/. HashiCorp says a nested module with a README is intended for external use, and one without is internal only. That is advisory, not enforced, so reviewers should look for accidental exposure: a nested module that callers import directly even though it has no README.
Call nested modules with relative paths, such as ./modules/consul-cluster. The documentation prefers composable nested modules over modules that call each other in deep trees. See flat module trees.
Examples go in an examples/ directory at the root. The guidance is to set module source addresses in examples to the external address a caller would use, not a relative path, because examples are often copied elsewhere. A reviewer can check the example does not point at ../...
README details
HashiCorp notes that tooling can generate input and output documentation, so the README does not need to list them by hand. It also advises using commit specific absolute URLs for links to files in the repository, so links keep working when the README is viewed outside the repository.
Changes that deserve extra attention
Beyond layout, ask these questions in every pull request:
- Does an input or output disappear or change type? That is a breaking change. See versioning with semver.
- Did a resource get renamed without a moved block?
- Did the minimum provider version change?
- Did a default change in a way that loosens a security setting?
- Did a new example or nested module appear without a README?
Make it routine
Put the checklist in a pull request template so authors tick it before review. Pair it with required reviewers from CODEOWNERS and a required status check. A template does not make anyone care, but it removes excuses.
Where Safeguard fits
Safeguard can enforce pull request policies and collect evidence that required reviews took place. The checklist above is still a human judgement about design.
Quick check
Is a README required to publish a module?
HashiCorp lists README.md as recommended. Only the root module is required by the structure page.
Should examples use relative source paths?
No. HashiCorp advises using the external address a caller would use, because examples are often copied elsewhere.
Sources
Last reviewed: 19 October 2026.