thomast1906/terraform-module-creator
Design and create Terraform modules from real infrastructure requirements. Use when asked to create a Terraform module, build a module from a requirement, turn repeated Terraform into a module, design module inputs and outputs, review whether a Terraform pattern should become a module, refactor existing Terraform into a reusable module, or assess whether a module is over-abstracted. Covers Azure-focused modules with KISS/DRY principles, module boundary definition, interface design, and practical file structure generation. Do NOT use for general Terraform coding with no module boundary, provider version upgrades (use terraform-provider-upgrade skill), or non-Terraform IaC.
npx skills add https://github.com/thomast1906/github-copilot-agent-skills --skill terraform-module-creator
Use this skill to help design and create Terraform modules from real infrastructure requirements.
This skill is intended to do more than generate Terraform files. It should help decide whether a module is justified in the first place, define a clear module responsibility, shape a clean interface, and avoid over-engineering.
The goal is to produce Terraform modules that are understandable, maintainable, and genuinely useful in practice.
The following tools must be installed locally before using this skill. The skill references them during generation and validation steps.
| Tool | Purpose | Install |
|------|---------|---------|
| Terraform | Core IaC tool — init, validate, fmt | brew install hashicorp/tap/terraform |
| terraform-docs | Generates README inputs/outputs from module source | brew install terraform-docs |
| tflint | Static analysis — catches deprecated args, provider-specific issues | brew install tflint |
| tflint-ruleset-azurerm | Azure-specific tflint rules | configured via .tflint.hcl (see VALIDATION.md) |
Minimum versions: Terraform ≥ 1.2 (required for lifecycle preconditions/postconditions). terraform-docs ≥ 0.16. tflint ≥ 0.50.
If any tool is missing, the skill will flag this and suggest the install command before running validation steps.
This skill makes active use of the HashiCorp Terraform Registry MCP and the Azure MCP Server. Use the tools below at the indicated stages of the workflow. Full parameter reference and example call sequences are in references/MCP-TOOLS.md.
| Tool | Stage | Purpose |
|------|-------|---------|
| azure-azureterraformbestpractices | Step 1 — Understand the ask | Get Azure Terraform best practices before starting design |
| azure-get_azure_bestpractices | Step 1 — Understand the ask | Get platform-level best practices for the specific Azure service |
| azure-documentation | Step 1 — Understand the ask | Look up CAF naming standards, DNS zone names, service feature behaviour |
| azure-wellarchitectedframework | Step 1 / Step 6 | Get WAF pillar recommendations to inform secure, reliable defaults |
| mcp_terraform_search_modules | Step 2 — Decide module type | Check if an Azure Verified Module or registry module already exists |
| mcp_terraform_get_module_details | Step 2 — Decide module type | Review an existing module's inputs/outputs to assess fit |
| mcp_terraform_get_provider_capabilities | Step 3 — Define boundary | Discover what azurerm resources exist for the service area |
| mcp_terraform_search_providers | Step 4 — Design interface | Find provider resource documentation by service slug |
| mcp_terraform_get_provider_details | Step 4 — Design interface | Get full argument reference and attribute reference for a resource |
| mcp_terraform_get_latest_provider_version | Step 6 — Generate module | Get the current latest azurerm version for versions.tf |
Important: Before invoking any Azure MCP tool, run a discovery call with learn=true or tool_search_tool_regex to confirm the exact tool name. Do not hardcode names that may change.
Always optimise for the following:
Follow this process for every request.
Call azure-azureterraformbestpractices and azure-get_azure_bestpractices for the target Azure service before starting design. Call azure-documentation for CAF naming conventions, private endpoint DNS zone names, or any service feature behaviour that needs current guidance. Call azure-wellarchitectedframework to understand which WAF pillar recommendations should inform the module's default values. This grounds decisions in current Azure platform guidance rather than assumptions.
Identify:
Where useful, restate the module intent in one sentence before generating anything.
Example:
> This module is intended to provision a standard Azure Storage Account pattern with consistent naming, tagging, diagnostics, and access configuration for shared platform use.
Check the Terraform Registry for reference. Call mcp_terraform_search_modules to see what existing modules look like for the resource. Use this to understand common interface patterns and what other teams have found necessary — not to replace the custom module. AVM and registry modules are often over-abstracted and do not align with the KISS principle this skill applies. Review them for ideas, not as a recommendation to adopt.
If a candidate is found, call mcp_terraform_get_module_details to review its interface. Use this to identify what arguments matter in practice and what complexity to avoid.
Work out whether the module is:
Prefer building block modules unless a composition module clearly maps to a repeated platform pattern. Do not create composition modules that bundle unrelated concerns together.
State clearly:
A good module boundary should be easy to explain in a few lines.
Use mcp_terraform_search_providers to find the resource documentation by service slug, then call mcp_terraform_get_provider_details with the returned provider_doc_id to get the full argument reference. Use this to understand all available arguments before deciding which to expose as variables. See references/MCP-TOOLS.md for common service slug values.
Design variables and outputs with discipline.
Variables
validation {} blocks to enum-style variables (SKU, tier, kind) and name format constraints — catch bad inputs at plan time with a clear error message, not a cryptic provider errorOutputs
sensitive = true on any output containing a secret: connection strings, access keys, passwords, SAS tokensprincipal_id (managed identity) over connection strings — guide consumers toward keyless authenticationLocals
Start simple. A typical module structure:
module/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── .terraform-docs.yml
├── README.md
└── examples/
└── basic/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
└── README.md
Add locals.tf if it improves clarity. Only add more files if there is a clear readability benefit. Do not create file sprawl for the sake of neatness.
Always create an examples/basic/ folder alongside the module. Each example is a standalone, independently runnable Terraform root module — it needs its own main.tf (the module call), variables.tf, outputs.tf, versions.tf, and README.md. The main.tf should demonstrate minimum viable usage with hardcoded values; keep variables.tf minimal and only expose what a caller genuinely needs to parameterise (e.g., resource group name, location). For modules with distinct usage patterns (e.g., with and without private endpoint), add a second subfolder such as examples/private-endpoint/ with the same full file set.
Always include .terraform-docs.yml — the README inputs and outputs table is generated by terraform-docs, not written by hand. The README contains a static description and usage example written by the author, with <!-- BEGIN_TF_DOCS --> / <!-- END_TF_DOCS --> markers where terraform-docs injects the generated content.
Before generating versions.tf, call mcp_terraform_get_latest_provider_version(namespace="hashicorp", name="azurerm") to get the current latest version and use it as the minimum constraint.
For modules with dependent optional variables (e.g., private_endpoint.enabled = true requires subnet_id), add lifecycle { precondition {} } blocks on the resource to surface misconfigurations clearly before any resource is created. See MODULE-PATTERNS.md for the pattern.
When generating code:
Always generate the following alongside the Terraform files:
.terraform-docs.yml — terraform-docs configuration (see MODULE-PATTERNS.md)README.md — static purpose description and usage example with <!-- BEGIN_TF_DOCS --> / <!-- END_TF_DOCS --> markers; do not hand-write the inputs/outputs table — that is generated by running terraform-docs .examples/basic/main.tf — the module call with sensible defaults; use hardcoded values appropriate for an example, not variablesexamples/basic/variables.tf — any inputs the example exposes to the caller (e.g., subscription ID, resource group name); keep this minimalexamples/basic/outputs.tf — relevant outputs from the module call (e.g., resource ID, connection string reference)examples/basic/versions.tf — provider required_providers block pinned to the same constraint as the module itself; the example is a real Terraform root and must be independently runnableexamples/basic/README.md — one-sentence description of what this example demonstratesAdd further subfolders (e.g., examples/private-endpoint/) for distinct usage patterns, each with the same full file set.
Tell the consumer to run terraform-docs . after any variable or output changes to regenerate the inputs/outputs table in the README. If a pre-commit hook is in use, provide the hook config from MODULE-PATTERNS.md.
Before finalising, check:
If the module feels hard to explain, it is probably doing too much.
Before handing off, run:
terraform fmt -check -recursive . # formatting
terraform init -backend=false && terraform validate # syntax and references
tflint --init && tflint # provider-specific issues, deprecated args, naming
terraform-docs . # README in sync with variables and outputs
Before running these commands, verify each tool is available (terraform -version, terraform-docs --version, tflint --version). If a tool is missing, output the install command from the Prerequisites section and stop — do not silently skip the validation step.
See references/VALIDATION.md for full tool setup, .tflint.hcl config, and CI pipeline guidance.
Before creating a module, stop and assess whether a module is actually the right answer.
Ask:
If a module is not justified, say so clearly. Do not create modules just because something appears more than once. A module should exist because it improves clarity, consistency, and supportability.
Depending on the request, produce one or more of the following:
If the user already has Terraform or a draft module, review it by asking:
Provide direct, practical feedback.
If the user has repeated Terraform code and wants to extract a module:
moved {} blocks for any renamed or restructured resources to prevent destructive plan changes for existing consumers (see MODULE-PATTERNS.md)When working on Azure Terraform modules, follow the platform standards in references/AZURE-STANDARDS.md. Key areas:
Do not assume a new custom module is always the best answer.
Apply these consistently:
Do not:
Supporting reference material for this skill:
moved blocks, tagging.tflint.hcl config), pre-commit hooks, CI pipeline exampleTake thomast1906/terraform-module-creator from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference brew.
Without those the skill loads but fails at the first command.