Registry #

The Terraform Registry at registry.terraform.io is a public repository containing thousands of ready-to-use modules — from VPC and EKS on AWS to Kubernetes clusters on GCP and Azure. Using modules from the Registry can save significant time, but only if the module you choose is truly right for your needs. This article covers how to navigate the Registry effectively, when to use it, and when it’s better to write your own module.

flowchart TD
    A["Terraform\nRegistry"] --> B["Official\nProviders"]
    A --> C["Verified\nProviders"]
    A --> D["Community\nModules"]

    B --> E["hashicorp/aws\nhashicorp/azurerm"]
    C --> F["terraform-aws-modules\nGoogle Cloud modules"]
    D --> G["Custom modules\nfrom the community"]

    style A fill:#3b82f6,stroke:#1e40af,color:#fff
    style B fill:#10b981,stroke:#059669,color:#fff
    style C fill:#f59e0b,stroke:#d97706,color:#fff
    style D fill:#8b5cf6,stroke:#6d28d9,color:#fff

What is the Terraform Registry #

The Terraform Registry is a central directory for:

registry.terraform.io contains:

PROVIDERS
  All providers that can be used in Terraform
  (hashicorp/aws, hashicorp/google, cloudflare/cloudflare, etc.)

MODULES
  Modules that can be called directly as child modules
  Categorized by provider and function

Two module categories in the Registry:

  VERIFIED ✓
    Written and maintained by HashiCorp or official partners
    Stricter quality review
    Example: hashicorp/consul/aws

  COMMUNITY
    Written by the community
    Quality varies — needs more careful evaluation
    Example: terraform-aws-modules/vpc/aws

terraform-aws-modules is the most widely used open-source module collection for AWS — written and maintained by an active community with hundreds of contributors.

# The most widely used VPC module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "production-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-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 = false  # Per-AZ NAT Gateway for HA

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

# The EKS module
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "production-eks"
  cluster_version = "1.29"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    general = {
      min_size     = 2
      max_size     = 5
      desired_size = 2
      instance_types = ["t3.medium"]
    }
  }
}

# The RDS module
module "rds" {
  source  = "terraform-aws-modules/rds/aws"
  version = "~> 6.0"

  identifier        = "production-db"
  engine            = "postgres"
  engine_version    = "15"
  instance_class    = "db.t3.medium"
  allocated_storage = 100

  db_name  = "appdb"
  username = "dbadmin"

  vpc_security_group_ids = [module.security_group.security_group_id]
  subnet_ids             = module.vpc.database_subnets
}

How to Evaluate a Module in the Registry #

Not every module in the Registry is worth using in production. Use these criteria before deciding.

MODULE EVALUATION CRITERIA:

TRUST:
  □ Is it from a known namespace? (terraform-aws-modules, hashicorp)
  □ Download count — popular modules are more tested
  □ Number of contributors and recent commit activity
  □ How many open issues are unresolved?

CODE QUALITY:
  □ Is there a complete README with examples?
  □ Are there examples/ files that can be run directly?
  □ Do variables have descriptions and validation?
  □ Do outputs cover all commonly needed attributes?

FIT:
  □ Does the module do exactly what you need?
  □ Is it too opinionated for your needs?
  □ Will you depend on features that might change?
  □ Is its license compatible with your organization's requirements?
flowchart LR
    A["terraform init"] -->|"Download"| B["📦 Module from the\nRegistry"]
    B -->|"Cached in\n.terraform/"| C["Used in the\nConfiguration"]

    style A fill:#3b82f6,stroke:#1e40af,color:#fff
    style B fill:#10b981,stroke:#059669,color:#fff
    style C fill:#f59e0b,stroke:#d97706,color:#fff

Private Registries #

For internal modules you don’t want to publish, organizations can use a private registry. There are several options.

# OPTION 1: Terraform Cloud / HCP Terraform Private Registry
# Modules are uploaded and managed via the UI or API
module "vpc" {
  source  = "app.terraform.io/my-org/vpc/aws"
  version = "~> 2.0"
  # Terraform Cloud handles authentication and versioning
}
# OPTION 2: Git repository with tags — the simplest
# No extra infrastructure needed
module "vpc" {
  source = "git::https://github.com/my-org/terraform-module-vpc.git?ref=v2.1.0"
}

# Or with SSH for private repos
module "vpc" {
  source = "git::ssh://[email protected]/my-org/terraform-module-vpc.git?ref=v2.1.0"
}
# OPTION 3: Gitea, GitLab, or Bitbucket as a module registry
# GitLab has a built-in Terraform Module Registry
module "vpc" {
  source  = "gitlab.example.com/infrastructure/vpc/aws"
  version = "~> 1.0"
}

When to Use the Registry vs Write Your Own #

USE A MODULE FROM THE REGISTRY IF:
  ✓ The module is proven and well-maintained (many downloads, active)
  ✓ Your needs match what the module does
  ✓ You have no security or compliance requirements forbidding
    the use of external code
  ✓ The module has enough flexibility via variables

WRITE YOUR OWN MODULE IF:
  ✗ Your needs are very specific and differ from what's in the Registry
  ✗ The available module is too complex with many features you don't need
  ✗ There are compliance requirements mandating an audit of all code
  ✗ The Registry module's interface changes too often and upgrades are disruptive
  ✗ Your customization needs are so deep it's easier to write from scratch

CONSIDER A HYBRID:
  ✓ Fork a public module and modify it for internal needs
    — you get a tested foundation, but full control
  ✓ Wrap a public module in an internal module
    — hide the public module's complexity behind a simpler interface
# Wrapper pattern: wrap a public module with a simpler interface
# modules/internal-vpc/main.tf

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  # Opinionated configuration for internal standards
  name = var.name
  cidr = var.cidr

  # AZs and subnets are calculated automatically — callers don't need to know these details
  azs             = data.aws_availability_zones.available.names
  private_subnets = [for k, v in data.aws_availability_zones.available.names : cidrsubnet(var.cidr, 4, k)]
  public_subnets  = [for k, v in data.aws_availability_zones.available.names : cidrsubnet(var.cidr, 4, k + 8)]

  # Internal standards that can't be overridden
  enable_nat_gateway   = true
  single_nat_gateway   = false
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = merge(var.tags, local.mandatory_tags)
}

# A much simpler interface for internal users
# Only needs 2 required variables:
variable "name" { type = string }
variable "cidr" { type = string }
variable "tags" { type = map(string); default = {} }


Publishing Modules to the Registry #

# Public Registry (registry.terraform.io):
# 1. Push to GitHub with a semver tag
git tag v1.0.0
git push origin v1.0.0
# 2. Log in to registry.terraform.io
# 3. Add module → Select the GitHub repo
# 4. Terraform automatically detects tags as versions

# Private Registry (Terraform Cloud):
# 1. Push to a VCS connected to TFC
# 2. TFC → Registry → Publish Module
# 3. The module is only accessible by your org
# Using a module from the registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"
  
  name = "my-vpc"
  cidr = "10.0.0.0/16"
  
  azs             = ["ap-southeast-1a", "ap-southeast-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
  
  enable_nat_gateway = true
}

Private Module Registry Setup #

# Setting up a private registry in Terraform Cloud:
# 1. Connect a VCS provider (GitHub/GitLab/Bitbucket)
# 2. Publish module → Select repository
# 3. Terraform auto-detects tags as versions
# 4. The module is available at: app.terraform.io/{org}/{module}/{provider}

# Using a module from the private registry
module "app" {
  source  = "app.terraform.io/my-org/app/aws"
  version = "~> 2.0"
}

Module Documentation #

# Auto-generate module documentation
brew install terraform-docs
terraform-docs markdown table . > README.md

# Example output:
# | Name | Description | Type | Default | Required |
# |------|-------------|------|---------|----------|
# | vpc_cidr | CIDR block for the VPC | string | n/a | yes |
# | environment | Environment name | string | n/a | yes |
# | enable_nat | Enable NAT gateway | bool | true | no |
# Publish a module with good documentation
# 1. Write a README.md with usage examples
# 2. All variables have descriptions
# 3. All outputs have descriptions
# 4. Include a CHANGELOG.md
# 5. Git tag with semver (v1.2.3)

Module Publishing Checklist #

BEFORE PUBLISHING:

□ All variables have descriptions
□ All outputs have descriptions
□ README.md is complete with usage examples
□ CHANGELOG.md records all changes
□ Tests pass (terraform validate, tflint)
□ Version tag with semver
□ License file present
□ .gitignore doesn't exclude needed files
□ Examples directory with usage examples
□ terraform-docs generates the documentation
# Pre-publish validation
terraform fmt -check
terraform validate
tflint .
terraform-docs markdown . > README.md

# Tag and publish
git tag -a v1.0.0 -m "Release v1.0.0: Initial release"
git push origin v1.0.0

Module Dependency Graph #

DEPENDENCY ORDER:

1. Foundation modules (no dependencies)
   - networking (VPC, subnets)
   - security (KMS, IAM)
   
2. Platform modules (depend on foundation)
   - compute (needs networking)
   - database (needs networking)
   
3. Application modules (depend on platform)
   - monitoring (needs compute, database)
   - ci-cd (needs compute)
# Module version pinning strategy
# The root module pins an exact version
module "networking" {
  source  = "app.terraform.io/my-org/networking/aws"
  version = "2.3.1"  # Exact pin for production
}

# Dev can use the latest
module "networking" {
  source  = "app.terraform.io/my-org/networking/aws"
  version = "~> 2.0"  # Allow minor updates
}

Summary #

  • The Terraform Registry contains thousands of providers and ready-to-use modules — the first place to check before writing a module from scratch.
  • terraform-aws-modules is the most trusted AWS module collection in the community — VPC, EKS, RDS, and dozens of other modules.
  • Evaluate modules carefully before using them in production — download count, maintenance activity, documentation quality, and fit with your needs.
  • Private registries can use Terraform Cloud, or simply a Git repository with tags for simpler needs.
  • Wrapper modules are a powerful pattern — wrap a public module with a simpler, more opinionated interface aligned with your organization’s internal standards.
  • Write your own if your needs are too specific, there are compliance requirements, or the available public modules are too complex for your needs.

← Previous: Interface Design   Next: What is an Environment? →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact