This article is based on original reporting from searchitoperations.techtarget.com. Some procedural details have been expanded for clarity.
Why Module Reuse Matters in Terraform
Software engineering has long promoted code reuse through reusable artifacts — libraries, packages, and modules. Most ecosystems provide a centralized registry for discovering and sharing these components: Python has the Python Package Index (PyPI), Node.js has npm, and PowerShell has the PowerShell Gallery.
Terraform follows the same pattern. The Terraform Registry is a centralized hub for distributing reusable Terraform modules — self-contained configuration packages that encapsulate a set of related infrastructure resources. Instead of writing the same resource definitions repeatedly across projects, teams can author a module once, publish it, and reference it wherever needed.
Public Registry vs. Private Registry
The Terraform Registry ships in two forms:
- Public Registry (registry.terraform.io) — hosts official provider plugins and community-contributed modules. Anyone can browse, use, and publish modules here.
- Private Registry — available as part of Terraform Cloud and Terraform Enterprise. Organizations use this to share modules internally without exposing them publicly.
This tutorial focuses on the public registry: how to create a module, meet the publishing requirements, and make it available for the broader Terraform community.
Prerequisites for Publishing to the Terraform Registry
HashiCorp enforces a set of requirements before a module can be listed in the public registry. Meeting these requirements also encourages good module hygiene that makes your code easier to maintain.
1. Public Version-Controlled Repository
The module must live in a public repository on a supported version control system. At the time of writing, the Terraform Registry supports GitHub, GitLab, Bitbucket, and Azure DevOps repositories. The repository must be publicly accessible so the registry can read its contents.
2. Repository Naming Convention
Repository names must follow this exact format:
terraform-<PROVIDER>-<NAME>Code language: HTML, XML (xml)
Where <PROVIDER> is the provider the module targets (e.g., aws, azurerm, google) and <NAME> describes what the module provisions. The <NAME> segment may contain hyphens. For example:
terraform-azurerm-resource-groupterraform-aws-vpcterraform-google-cloud-run
3. Repository Description
The repository’s description field (the short blurb on the repository home page) must be populated. The registry surfaces this text in search results, so write a concise, accurate summary of what the module does.
4. Standard Module Structure
HashiCorp defines a standard directory layout that the registry parses to generate documentation and track inputs, outputs, and resources. At a minimum, a module should include:
.
├── main.tf # Core resource definitions
├── variables.tf # Input variable declarations
├── outputs.tf # Output value declarations
└── README.md # Human-readable documentationCode language: CSS (css)
Larger modules may split logic into sub-modules under a modules/ subdirectory and include example configurations under an examples/ subdirectory. The registry will render these automatically.
5. Semantic Version Tags
The registry identifies releases through Git tags that follow semantic versioning (e.g., v1.0.0, v1.2.3). At least one valid version tag must exist before you can publish. Without a tag, the registry has no release to index.
Step 1 — Create the GitHub Repository
Start by creating a new public repository on GitHub. For an Azure resource group module, name it terraform-azurerm-rg (or a more descriptive variant such as terraform-azurerm-resource-group).
- Set visibility to Public.
- Add a README.md to initialize the repo.
- Add a .gitignore file using the Terraform template (this excludes
.terraform/directories and state files). - Choose a license — MIT is common for open-source Terraform modules.
- Fill in the repository description field before clicking Create.
Step 2 — Clone the Repository and Add Module Files
Clone the new repository to your local workstation and open it in your editor of choice (VS Code works well with the HashiCorp Terraform extension).
git clone https://github.com/<your-org>/terraform-azurerm-rg.git
cd terraform-azurerm-rgCode language: PHP (php)
Create the three core files:
main.tf
resource "azurerm_resource_group" "this" {
name = var.name
location = var.location
tags = var.tags
}Code language: JavaScript (javascript)
variables.tf
variable "name" {
description = "The name of the Azure resource group."
type = string
}
variable "location" {
description = "The Azure region in which to create the resource group."
type = string
}
variable "tags" {
description = "A map of tags to apply to the resource group."
type = map(string)
default = {}
}Code language: JavaScript (javascript)
outputs.tf
output "id" {
description = "The ID of the resource group."
value = azurerm_resource_group.this.id
}
output "name" {
description = "The name of the resource group."
value = azurerm_resource_group.this.name
}
output "location" {
description = "The location of the resource group."
value = azurerm_resource_group.this.location
}Code language: JavaScript (javascript)
Step 3 — Test the Module Locally
Before publishing, validate that the module actually works by referencing it from a local Terraform configuration. Create a separate directory (outside the module repo) for your test configuration:
# File: ../test-rg/main.tf
module "resource_group" {
source = "../terraform-azurerm-rg"
name = "my-test-rg"
location = "eastus"
tags = {
environment = "dev"
}
}Code language: PHP (php)
Run the following commands from the test directory:
terraform init
terraform plan
A clean plan output confirms the module is structured correctly. Run terraform apply (with valid Azure credentials configured) to do a full end-to-end test against a real Azure subscription.
Step 4 — Commit, Tag, and Push
Return to the module repository directory, commit all files, and push them to GitHub:
git add .
git commit -m "Initial module release"
git push origin mainCode language: JavaScript (javascript)
Now create and push a semantic version tag. The v prefix is required by the Terraform Registry:
git tag v1.0.0
git push origin v1.0.0Code language: CSS (css)
Without at least one tag, the registry will not index your module releases.
Step 5 — Publish to the Public Terraform Registry
Publishing a module is a one-time action per repository. The steps below only need to be performed once; subsequent releases are picked up automatically when you push new version tags.
- Sign in to registry.terraform.io using your GitHub account.
- Click Publish in the top navigation bar, then select Module.
- The registry will list the public GitHub repositories in your account that match the
terraform-<PROVIDER>-<NAME>naming pattern. Select your repository. - Agree to the publishing terms and click Publish Module.
- The registry will scan the repository, parse the module structure, and index the version tag. Within a few minutes, the module will appear at a URL similar to
registry.terraform.io/modules/<namespace>/rg/azurerm.
From this point forward, any new vX.Y.Z tag you push to the GitHub repository will automatically appear as a new release in the registry — no additional publishing steps required.
Consuming the Published Module
Once published, any Terraform user can consume the module by specifying the registry source:
module "resource_group" {
source = "<namespace>/rg/azurerm"
version = "1.0.0"
name = "prod-rg"
location = "westeurope"
tags = {
environment = "production"
}
}Code language: JavaScript (javascript)
The registry page for the module will display auto-generated documentation for all inputs, outputs, and resources — pulled directly from the variable and output descriptions in your variables.tf and outputs.tf files. Writing good descriptions in those files is therefore essential.
Best Practices for Terraform Module Authoring
- Pin provider versions in a
versions.tffile inside the module to signal which provider versions the module has been tested against. - Write descriptive variable and output descriptions — the registry renders these as documentation.
- Include an
examples/directory with at least one working example configuration. The registry surfaces examples on the module page. - Use semantic versioning meaningfully: increment patch versions for bug fixes, minor versions for backwards-compatible additions, and major versions for breaking changes.
- Add a
CHANGELOG.mdto help consumers understand what changed between versions. - Validate with
terraform validateand lint withtflintas part of your CI pipeline before tagging a release.
Summary
Publishing a module to the Terraform Registry requires a properly named public GitHub repository, a standard module file structure, a populated repository description, and at least one semantic version tag. Once these prerequisites are met, the actual publishing step is a one-time operation through the registry UI. All subsequent releases are handled automatically via Git tags, making ongoing module maintenance straightforward for your team and your consumers.