Give a Terraform module its own repository when several configurations will reuse it and you want to release it on a schedule that differs from the code that calls it. Keep it local to the calling repository when only one stack uses it. HashiCorp's guidance points the same way.
What the documentation says
HashiCorp's module development page says that if many configurations will reuse a module, you should consider placing it in its own version control repository. It also warns against over-using modules, because they can make a configuration harder to understand and maintain.
The same page cautions against thin wrappers, which are modules that only wrap one resource type. If you struggle to name a module differently from the resource inside it, the module probably adds no abstraction and the calling code should use the resource directly.
Why this is a security question
A shared module is code that many teams run with their own credentials. A change to it reaches every caller that does not pin a version. So the question of where it lives is really a question of who can change it and how callers hear about the change.
A module inside an application repository inherits that repository's reviewers, which may be the wrong people. A module in its own repository can have its own owners, its own approval rules and its own tags. That separation is what makes tight review practical.
A decision table
| Situation | Suggested home |
|---|---|
| One stack uses it, one team owns both | Local folder in the calling repository |
| Two or three stacks, same team | Local folder, revisit when a third team appears |
| Several teams call it | Own repository with tagged releases |
| It encodes a security baseline (network, IAM, logging) | Own repository with named owners |
| It wraps a single resource with no extra logic | No module, use the resource |
What changes when you split it out
Moving a module into its own repository adds some overhead, and it is worth knowing the cost before you start.
- Callers switch from a relative path to a registry or git source, and must pin it. See pinning Terraform modules.
- You need a release habit. Tags should be semantic versions, and the public registry requires that format.
- You need owners for the repository, with review rules that match its blast radius. CODEOWNERS for workflow and release files is a good place to start.
- You need a way to tell callers about breaking changes, such as release notes and a clear major version bump.
Docs also note that examples inside a module repository should use the external address a caller would use, not a relative path, because examples get copied elsewhere.
A middle path
If a split feels premature, keep the module in a modules/ folder of a shared infrastructure repository with its own CODEOWNERS entry. You get separate reviewers without separate release machinery. Move it out when callers outside that repository need it.
Where Safeguard fits
Safeguard can enforce policies and gates on pull requests, including those to infrastructure repositories, and its compliance and evidence automation can record who approved a change. It does not replace choosing sensible owners. You can run a free scan at gold.safeguard.sh/scan.
Quick check
Is one module per repository required?
No. Terraform allows modules in subfolders or remote repositories. A separate repository is a design choice that helps when many configurations reuse a module.
Should every resource get a module?
No. HashiCorp advises against thin wrappers around a single resource type, because they add indirection without adding an abstraction.
Sources
Last reviewed: 19 October 2026.