Skip to main content

Terraform Registry — Using Community Modules and Publishing Your Own

Use the Terraform Registry to find battle-tested community modules for AWS, GCP, and Azure — and learn how to publish and version your own private modules.

26 Terms

Overview and What You Will Learn

The Terraform Registry, at registry.terraform.io, is like npm for infrastructure. Someone has almost certainly already written a well-tested, widely-used module for the thing you are about to build — an EKS cluster, a VPC, an RDS instance — and it has been used and fixed by thousands of other engineers before you ever typed a single line. Writing your own from scratch when a mature community module already exists is usually wasted effort.

By the end of this lab you will be able to:

  • Search the Terraform Registry and judge whether a community module is trustworthy
  • Use a registry module with the correct source address and version constraint
  • Read a module's documentation to find required inputs, optional inputs, and outputs
  • Understand private registries in Terraform Cloud for company-internal modules
  • Publish your own module to the public registry, following its naming rules
  • Choose a sane versioning strategy so you don't break every consumer with one release

Why This Matters in Production

At Meesho, a new platform engineer was assigned to set up an EKS cluster from scratch. Writing the IAM roles, node groups, security groups, and OIDC provider configuration by hand would have taken roughly two weeks and almost certainly missed a few of AWS's more obscure EKS requirements. Instead, the engineer used terraform-aws-modules/eks/aws — a community module already used in production by thousands of companies — and had a working cluster in an afternoon. Six months later, when AWS deprecated a node group API the module was using, the module's maintainers shipped a fix within days. Meesho bumped the version constraint and got the fix for free, without ever touching their own code.

This is the registry's real value: you are not just borrowing code, you are borrowing thousands of other engineers' bug reports.

Core Principles

How Module Source Resolves from Registry to Local Cache
Bash
+------------------------------------------------+
| Your module block: |
| module "vpc" { |
| source = "terraform-aws-modules/vpc/aws" |
| version = "~> 5.0" |
| } |
+------------------------------------------------+
|
terraform init reads source + version
|
v
+------------------------------------------------+
| registry.terraform.io resolves the address to |
| the latest version matching "~> 5.0" |
| (e.g. it picks 5.8.1) |
+------------------------------------------------+
|
downloads module source code
|
v
+------------------------------------------------+
| Cached locally in: |
| .terraform/modules/vpc/ |
| (committed to .gitignore — never check this in) |
+------------------------------------------------+

Detailed Step-by-Step Practical Lab

Step 1 — Search the Registry and Evaluate Trustworthiness

Before using any community module, check these signals on its registry page:

Bash
Things to check on a module's registry.terraform.io page:
1. Verified badge -> published by the provider's official partner team
2. Download count -> tens of millions = battle-tested at massive scale
3. Last published date -> updated within the last few months = actively maintained
4. GitHub stars/issues -> click through to the source repo, check open issue count
5. Number of releases -> mature modules have 30+ releases, not version 1.0.0 from 2019
Note

Tip from a senior engineer: a module with 50 million downloads and a verified badge, like terraform-aws-modules/vpc/aws, has effectively been tested in more production environments than your entire company could test in years. That track record is worth more than reading every line of its source code yourself.

Step 2 — Use a Registry Module with Source and Version
HCL
module "vpc" {
source = "terraform-aws-modules/vpc/aws" # namespace/name/provider — three parts, no slashes added
version = "~> 5.0" # see version constraint syntax below
name = "phonepay-prod-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-south-1a", "ap-south-1b", "ap-south-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = true # cost optimization — one NAT gateway shared across AZs
}
Step 3 — Understand Version Constraint Syntax
HCL
version = "5.8.1" # EXACT version only — no flexibility, rarely used in modules
version = "~> 5.0" # "pessimistic constraint" — allows 5.0.x and 5.1.x, NOT 6.0.0
version = "~> 5.8" # allows 5.8.x only (last segment can change, not the second)
version = ">= 5.0" # any version 5.0 or above, including future major versions — risky
version = ">= 5.0, < 6.0" # explicit range — same effect as "~> 5.0" but more readable
Bash
terraform init -upgrade
# Terraform will print which version it actually selected within your constraint
Common mistake

Common mistake: using >= 5.0 with no upper bound. This silently allows Terraform to pull in version 6.0.0 — which might rename half its variables — the next time someone runs terraform init -upgrade. Always cap the major version with ~> or an explicit < 6.0.

HCL
# VPC — the most-used module on the entire registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
}
# EKS — handles the node groups, OIDC provider, and IAM roles correctly
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
}
# RDS — wraps aws_db_instance with sane defaults for parameter groups, subnet groups
module "rds" {
source = "terraform-aws-modules/rds/aws"
version = "~> 6.0"
}
# S3 bucket — adds versioning, encryption, lifecycle rules as simple toggles
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 4.0"
}
Step 5 — Read Module Documentation Properly

Every registry module page has three tabs that matter: Inputs, Outputs, and Resources. Before calling any module, check:

◈ DIAGRAM
1. Inputs tab -> which variables have NO default (these are REQUIRED)
2. Inputs tab -> which variables have a default you should override for production
3. Outputs tab -> what values you can chain into your own resources afterward
4. Resources tab -> what this module actually creates under the hood — useful
when debugging an unexpected AWS resource in your account
HCL
# Example: reading the EKS module's outputs to wire up kubectl access afterward
output "cluster_endpoint" {
value = module.eks.cluster_endpoint # straight from the module's documented outputs
}
output "cluster_certificate_authority_data" {
value = module.eks.cluster_certificate_authority_data
}
Step 6 — Private Module Registries for Internal Modules

Public registry modules are great for generic infrastructure. But your company's specific patterns — like "how we configure logging for every Lambda function" — belong in a private registry, available only inside Terraform Cloud or Terraform Enterprise:

HCL
module "lambda_with_logging" {
source = "app.terraform.io/zerodha-org/lambda-standard/aws"
# ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^
# your TFC hostname your organisation name
version = "2.1.0"
}

Private registries give you the same version constraint syntax, documentation tabs, and dependency resolution as the public registry — just scoped to modules your company publishes internally.

Step 7 — Publish Your Own Module to the Public Registry

The public registry has strict naming and structure rules. Your GitHub repository must be named exactly terraform-<PROVIDER>-<NAME>:

Bash
Correct repo name: terraform-aws-ecs-service
Incorrect repo name: ecs-service-module
Incorrect repo name: aws-terraform-ecs-service (wrong word order)
Bash
# Repository requirements before the registry will accept it:
# 1. Repo name matches terraform-<PROVIDER>-<NAME> exactly
# 2. Repo is public on GitHub
# 3. A README.md exists at the root
# 4. main.tf, variables.tf, outputs.tf exist at the root (not nested in a subfolder)
# 5. At least one Git release tag exists, formatted as a semantic version (vX.Y.Z)
git tag v1.0.0
git push origin v1.0.0
# Then go to registry.terraform.io, sign in with GitHub,
# and click "Publish" — the registry pulls releases automatically from then on
Note

Tip: once published, the registry watches your GitHub releases automatically. Every time you push a new vX.Y.Z tag, it appears on the registry within minutes — you never manually upload anything.

Step 8 — Choose a Versioning Strategy

Use semantic versioning — MAJOR.MINOR.PATCH — and mean it:

◈ DIAGRAM
v1.0.0 -> v1.0.1 PATCH : bug fix, no behavior change for any existing caller
v1.0.0 -> v1.1.0 MINOR : new optional variable added, fully backward compatible
v1.0.0 -> v2.0.0 MAJOR : a required variable was renamed, removed, or its type changed
— anyone on v1.x must change their code to upgrade
HCL
# Consumers protect themselves from breaking changes with the right constraint
version = "~> 1.5" # gets all 1.x patch and minor updates, but NEVER auto-jumps to v2.0.0

Production Best Practices and Common Pitfalls

  • Read the CHANGELOG before any major version bump. A jump from ~> 4.0 to ~> 5.0 on a popular module like terraform-aws-modules/vpc/aws can rename several variables. Always read the module's changelog or release notes before widening a version constraint across a major boundary.

  • Pin exact versions in CI, looser ranges during local development. Many teams use ~> 5.0 while developing, but lock the resolved version with a committed .terraform.lock.hcl file so CI always uses exactly what was tested — never a newer patch release that happened to publish overnight.

  • Don't fork a community module just to add one variable. Forking means you now own every future security fix and AWS API change yourself. Check if the module supports null overrides or a for_each-based extension pattern first — most well-maintained modules already have a way to extend behavior without forking.

  • Verify before trusting, every time — even for "verified" modules. A verified badge means the publisher's identity is confirmed, not that the code is bug-free. Still read the Resources tab to understand exactly what it creates in your AWS account before running apply against production.

Quick Reference and Troubleshooting Commands

Constraint Syntax Meaning
"5.8.1" Exact version only
"~> 5.8" Allows 5.8.x patch updates only
"~> 5.0" Allows any 5.x version, blocks 6.0.0
">= 5.0, < 6.0" Explicit range, same effect as ~> 5.0
Error Root Cause Fix
No available releases match Version constraint too narrow, or typo in constraint Check the registry page for actual available versions
Module not found in registry Wrong namespace/name/provider order in source Re-check the exact three-part address on the registry page
Unexpected resources appear after upgrade Jumped across a major version with breaking defaults Read the module's CHANGELOG before widening version constraints
Failed to install provider after module upgrade New module version requires a newer provider than your root config allows Bump required_providers version range in your root versions.tf

Resources

Explore More in Terraform Modules and Code Organisation

All 3 Topics

Frequently Asked Questions

Is Terraform Registry — Using Community Modules and Publishing Your Own free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Terraform Registry — Using Community Modules and Publishing Your Own topic cover?

Use the Terraform Registry to find battle-tested community modules for AWS, GCP, and Azure — and learn how to publish and version your own private modules.