Usually not. HashiCorp advises against modules that are just thin wrappers around a single resource type. A wrapper adds a version to track, a repository to review and a layer to read through, without adding an abstraction. A wrapper is justified only when it adds a policy.
What the documentation says
HashiCorp's module development page says a good module groups resources that work together and represent a higher level concept in your architecture, and that modules can be over-used, making configurations harder to understand and maintain.
It gives a naming test: if you struggle to name a module differently from the resource inside it, the module probably adds no abstraction, and you should use the resource directly in the calling module.
The review cost people forget
Every shared module has a cost that shows up in review and operations:
- A release process, with tags and notes.
- Owners who must stay responsive.
- Callers who must bump pins, and bots raising pull requests for them.
- An extra hop for anyone reading a plan, who must open the module to learn what it does.
For a wrapper that only passes arguments through, that cost buys nothing.
When a wrapper is reasonable
The honest exception is a module that exists to enforce a rule, not to rename a resource. The module still wraps one resource type, but it bakes in a baseline: encryption on, public access off, mandatory tags, logging configured. Its value is the opinion, not the abstraction.
Even then, test the claim. Does the module remove arguments from the interface so callers cannot loosen the setting? If it simply exposes every argument again, it is a wrapper with extra steps.
| Question | If yes | If no |
|---|---|---|
| Does it group several resources into one concept? | Good module | Keep asking |
| Does it enforce a baseline callers cannot loosen? | Worth keeping, document the rule | Probably a thin wrapper |
| Can you name it something other than the resource? | Likely adds abstraction | Use the resource directly |
| Do more than one team call it? | Shared cost is spread | Local code may be simpler |
What to do with an existing wrapper
- List its inputs and see how many pass straight through unchanged.
- If most do, inline the resource in callers during normal changes.
- If the baseline is the value, shrink the interface to the settings callers legitimately need, and add validation for the rest. See variable validation guardrails.
- Keep a moved block so Terraform maps the resource to its new address and does not plan a replacement. HashiCorp notes refactoring blocks record changes in resource names and module structure for this reason.
- Read the plan carefully after any refactor. A clean plan with no changes is the target.
Alternatives to a wrapper
If the goal is policy, consider other places to enforce it, such as checks that run over the plan. See checking a Terraform plan with OPA. That lets teams use resources directly while still meeting the rule. Each approach has costs, so weigh them against how many teams you support.
Where Safeguard fits
Safeguard policies and gates apply at the pull request, and its evidence automation can record that checks ran. It does not scan Terraform for misconfigurations, so your baseline rule needs its own enforcement.
Quick check
What is a thin wrapper?
HashiCorp uses the term for modules that only wrap a single other resource type without adding an abstraction.
How do I avoid destroying resources when I remove a wrapper?
Use a moved block so Terraform maps the existing object to its new address, and read the plan before applying.
Sources
Last reviewed: 19 October 2026.