Terraform module author
Authors reusable, production-grade Terraform modules with clean typed variables, well-documented outputs, version pinning, state hygiene, and built-in validation. Use this skill when the user asks to "write a Terraform module", "refactor Terraform into a module", "add variable validation", "structure a Terraform repo", "publish a module to the registry", "review my .tf files", or otherwise create, organize, or harden HCL infrastructure code.From its SKILL.md
npx -y skills add JayRHa/AgentSkills --skill terraform-module-authorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.1 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it
Terraform Module Author
Overview
This skill encodes the conventions for writing reusable Terraform modules that are safe to share, version, and consume across teams. It covers file layout, typed variables with validation, outputs, provider/version pinning, state hygiene, and documentation.
Keywords: terraform, opentofu, hcl, module, variables, outputs, validation, terraform.tfvars, versions.tf, provider pinning, remote state, registry, tflint, terraform fmt, terraform validate, infrastructure as code, IaC, DevOps.
A "module" here is any directory of .tf files meant to be called by another configuration via a module "x" { source = ... } block. The same rules apply to the root module — the only difference is the root configures providers and backends, while reusable child modules must NOT.
Workflow
- Clarify the boundary. Decide what the module owns. A good module manages one logical unit (a VPC, an S3 bucket with policy, a Kubernetes namespace). If you cannot name it in one phrase, split it. List required inputs, optional inputs, and what callers need back as outputs.
- Lay out the standard files. Create
main.tf,variables.tf,outputs.tf,versions.tf, andREADME.md. Keep resource logic inmain.tf(or split by concern, e.g.iam.tf,network.tf). Never put variables or outputs inline inmain.tf. Seereferences/module-conventions.md. - Pin versions in
versions.tf. Setrequired_versionfor Terraform/OpenTofu andrequired_providerswith a source and a~>pessimistic constraint. Reusable child modules declarerequired_providersbut do NOT includeproviderblocks orbackendconfig — those belong only to the root. - Write typed variables with validation. Every variable gets an explicit
type, adescription, and adefaultonly when truly optional. Addvalidationblocks for enums, ranges, regex, and naming rules. Mark secretssensitive = true. Seereferences/variables-and-validation.md. - Expose minimal, stable outputs. Output the IDs/ARNs/endpoints callers need to wire things together. Every output has a
description. Mark sensitive outputssensitive = true. Avoid leaking the entire resource object unless intentional. - Guard state hygiene. Use
for_eachovercountfor named, stable resources. Never hardcode backends inside reusable modules. Document anymoved {}blocks when renaming resources to avoid destroy/recreate. Seereferences/state-hygiene.md. - Validate and lint. Run
terraform fmt -recursive,terraform validate, andtflint. Use the bundledscripts/validate_module.shto run the full gate in one command. - Document. Fill in
README.mdfromtemplates/README.md.tmpl— purpose, usage example, and an inputs/outputs table. Ifterraform-docsis available, generate the tables automatically.
Module Layout (canonical)
my-module/
├── main.tf # resources and locals
├── variables.tf # all input variables, typed + validated
├── outputs.tf # all outputs, described
├── versions.tf # required_version + required_providers (no provider/backend)
├── README.md # purpose, usage, inputs/outputs tables
├── examples/
│ └── basic/ # a runnable example that calls the module
└── tests/ # optional: terraform test (.tftest.hcl) or terratest
Decision Framework: count vs for_each
| Situation | Use | Why |
|---|---|---|
| Toggle a single resource on/off | count = var.enabled ? 1 : 0 | Simple boolean gate |
| N identical, order-insensitive copies | for_each over a set | Stable keys survive list reordering |
| Named/keyed resources (buckets, users) | for_each over a map | Renaming/removing one item won't reindex the rest |
| A list that may be reordered | NOT count | count keys by index; reordering forces destroy/recreate |
Rule of thumb: prefer for_each whenever items have a natural identity. Reserve count for on/off toggles.
Variable Validation Cheatsheet
variable "environment" {
type = string
description = "Deployment environment."
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be one of: dev, staging, prod."
}
}
variable "instance_count" {
type = number
description = "Number of instances (1-10)."
default = 1
validation {
condition = var.instance_count >= 1 && var.instance_count <= 10
error_message = "instance_count must be between 1 and 10."
}
}
variable "name" {
type = string
description = "Resource base name (lowercase, hyphen-separated)."
validation {
condition = can(regex("^[a-z][a-z0-9-]{1,30}[a-z0-9]$", var.name))
error_message = "name must be 3-32 chars, lowercase alphanumeric and hyphens, not starting/ending with a hyphen."
}
}
See references/variables-and-validation.md for object types, optional() attributes with defaults, cross-variable validation, and precondition/postcondition checks.
Worked Example
examples/s3-bucket-module.md shows a complete reusable S3 bucket module: typed variables with validation, for_each lifecycle rules, sensitive outputs, version pinning, and a calling example. Use it as a reference shape when authoring new modules.
Best Practices
- One responsibility per module. Compose small modules in the root rather than building one mega-module with dozens of feature flags.
- No providers or backends in child modules. Declare
required_providersonly. The root passes providers in (implicitly or viaproviders = {}) and owns the backend. - Pin everything.
required_version, every provider, and any nestedsource(use a?ref=tagor registry version). Unpinned modules break silently on upstream changes. - Describe every variable and output. The description is the API contract;
terraform-docsrenders it. - Validate at the boundary. Catch bad input with
validationblocks rather than letting the provider fail mid-apply with a cryptic error. - Prefer
for_eachwith stable keys so adding/removing one item never reshuffles others. - Mark secrets
sensitiveon both variables and outputs to keep them out of plan output and logs. - Use
localsfor computed/derived values, not for things that should be inputs. Tag merging (merge(var.tags, local.common_tags)) belongs in locals. - Keep examples runnable. An
examples/basicthat actuallyterraform plans is your best regression test and documentation. - Run the gate before commit:
scripts/validate_module.sh.
Common Pitfalls
- Backend/provider blocks inside a reusable module. This makes it un-composable and causes "provider configuration not allowed" or duplicate backend errors. Move them to the root.
- Using
countfor keyed resources. Removing the first item in a list re-indexes everything and triggers needless destroy/recreate. Usefor_each. - Untyped variables (
type = anyeverywhere). Loses validation and self-documentation. Type explicitly; useobject({...})for structured input. - Outputting nothing useful. Callers can't reference IDs/ARNs that aren't exported. Output the wiring points.
- Renaming resources without
moved {}. Terraform sees a delete + create. Add amoved {}block to preserve state. terraform.tfvarscommitted with secrets. Never commit real secrets; pass via env (TF_VAR_*), a secrets manager, or-var-fileexcluded from VCS.- Mutable default tags overriding caller tags. Always
merge()with caller-supplied tags taking precedence. - Skipping
terraform fmt/validatein CI. Drift in formatting and silent config errors accumulate. Gate every PR.
What ships with it: 6 files
18.8 KB alongside SKILL.md, 1 of them executable
examples/
- s3-bucket-module.md4.8 KB
references/
- module-conventions.md3.5 KB
- state-hygiene.md3.3 KB
- variables-and-validation.md3.4 KB
scripts/
- validate_module.shruns2.2 KB
templates/
- README.md.tmpl1.6 KB