TF Modules: How to Use & Create
Learn how to create and use reusable TF modules for scalable, maintainable, and shareable infrastructure configurations. Best practices and versioning tips included.
Terraform Modules: How to Use & Create
Terraform modules - 11%
Objective
- Contrast and use different module source options including the public Terraform Module Registry
- Interact with module
inputsandoutputs - Describe
variable scopewithin modules/child modules - Set module version
Modules
Small, reusable Terraform configurations that let you manage a group of related resources as if they were a single resource.
A collection of resources that make up a specific piece of infrastructure
- โ
Initialize & download (providers + modules) using:
terraform init - โ
Refresh/download just the modules (without a full re-init) using:
terraform get
โโโ LICENSE
โโโ README.md
โโโ main.tf
โโโ variables.tf
โโโ outputs.tf
- Organize configuration
- Encapsulate configuration
- Re-use configuration
Loading Modules
Terraform cache these modules in the .terraform/modules subdirectory in the current working directory
Modules can either be loaded from the local filesystem directory
module "website_s3_bucket" {
source = "./modules/aws-s3-static-website-bucket"
}
or a remote source such as
Terraform Registry- version control systems,
- HTTP URLs
Terraform CloudTerraform Enterpriseprivate module registries.
Verified Module
Reviewed by Hashicorp & maintined by contributors
- Verified badge is shown for official modules
- Unverified module does not mean low quality and vice versa
Note: Only the official or verified modules are visible in Terraform Registry search results
Child Module
A module that is called by another configuration is referred to as a
child moduleof that configuration.
module "servers" {
source = "./app-cluster" # Child Module
}
Output
module.<MODULE NAME>.<OUTPUT NAME>
Access IP address of
module.web_server.instance_ip_addr.
Supported Arguments
- The
sourceargument is mandatory for all modules. - The
versionargument is recommended for modules from a registry. - Meta-arguments
count- Creates multiple instances of a module from a single module block.for_each- Creates multiple instances of a module from a single module block.providers- Passes provider configurations to a child module.depends_on- Creates explicit dependencies between the entire module and the listed targets.
Public Module
downloadeble from
Terrform registry
- Accesses by
<Namespace>/<Name>/<Provider>
Publishing a Public module
- โ The module must be on GitHub and must be a public repo. Not needed for private module
- โ x.y.z tags for releases: at least one release tag must be present e.g. v1.0.4 and 0.9.2
- โ
Module repositories must use this three-part name format,
terraform-<PROVIDER>-<NAME>e.g. terraform-aws-vpc - Follow standard module structure.
Private Module
Downloadable from
Terrform Cloudafterterrform loginor using API token in CLI
- Accesses by
<HostName>/<Namespace>/<Name>/<Provider>
Module Sources
Local paths
#local module
module "servers" {
source = "./app-cluster"
}
From Terraform Registry
# Public module
# <Namespace>/<Name>/<Provider>
module "consul" {
source = "hashicorp/consul/aws"
version = "0.0.5"
}
# Private Module
# <HostName>/<Namespace>/<Name>/<Provider>
module "consul" {
source = "app.terraform.io/example-corp/k8s-cluster/azurerm"
version = "1.1.0"
}
Github
# HTTP
module "consul" {
source = "github.com/hashicorp/example"
}
# SSH
module "consul" {
source = "git@github.com:hashicorp/example.git"
}
# select a specific tag
module "vpc" {
source = "git::https://example.com/vpc.git?ref=v1.2.0"
}
# Generic Git repo with HTTP
module "vpc" {
source = "git::https://example.com/vpc.git"
}
# Generic Git repo with SSH
module "storage" {
source = "git::ssh://username@example.com/storage.git"
}
# directly select a commit using its SHA-1 hash
module "storage" {
source = "git::https://example.com/storage.git?ref=51d462976d84fdea54b47d80dcabbf680badcdb8"
}
BitBucket
module "consul" {
source = "bitbucket.org/hashicorp/terraform-consul-aws"
}
HTTP
module "vpc" {
source = "https://example.com/vpc-module?archive=zip"
}
Supported Zipped format
- zip
- tar.bz2 and tbz2
- tar.gz and tgz
- tar.xz and txz
S3 Bucket
module "consul" {
source = "s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"
}
GCP bucket
module "consul" {
source = "gcs::https://www.googleapis.com/storage/v1/modules/foomodule.zip"
}
count vs for_each in Modules
Prefer for_each over count when creating multiple module instances:
# count โ instances addressed by index (fragile: deleting middle element shifts indices)
module "bucket" {
count = 3
source = "./s3-bucket"
name = "bucket-${count.index}"
}
# addressed as: module.bucket[0], module.bucket[1], module.bucket[2]
# for_each โ instances addressed by key (stable: deleting one key doesn't affect others)
module "bucket" {
for_each = toset(["logs", "assets", "backups"])
source = "./s3-bucket"
name = "${each.key}-bucket"
}
# addressed as: module.bucket["logs"], module.bucket["assets"], module.bucket["backups"]
If you remove "assets" from the for_each set, only module.bucket["assets"] is destroyed. With count, removing element 1 would shift index 2 โ 1, causing Terraform to plan a destroy+recreate on what was module.bucket[2].
Related Posts
- Terraform Configuration Management โ variables, outputs,
for_each, and lifecycle blocks that modules use internally - IaC Concepts & TF Overview โ the reuse and encapsulation benefits of modules explained in the broader IaC context
- TF State & Backend Management โ modules share the root module's state;
terraform state mvcan move resources between modules - TF Cloud Capabilities & Workflow โ Terraform Cloud's private module registry for sharing modules across teams
