Terraform Cheatsheet

Modules

Use this Terraform reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

What Modules Are

A module is any directory with .tf files. Every Terraform project is the root module. You call child modules to encapsulate reusable infrastructure patterns.

Calling a Module

module "vpc" {
  source  = "./modules/vpc"         # local path
  # source  = "hashicorp/vpc/aws"   # Terraform Registry
  # source  = "git::https://github.com/org/modules.git//vpc?ref=v1.2.0"

  # pass values to the module's input variables
  cidr_block   = "10.0.0.0/16"
  environment  = var.environment
  az_count     = 3
}

# Consume module outputs
output "vpc_id" {
  value = module.vpc.vpc_id
}

Module Sources

Source typeExample
Local path./modules/vpc
Terraform Registry"terraform-aws-modules/vpc/aws"
Registry with versionversion = "5.1.0" (separate arg)
GitHub HTTPS"git::https://github.com/org/repo.git//subdir"
GitHub SSH"git::ssh://git@github.com/org/repo.git?ref=v1.0"
S3 bucket"s3::https://s3.amazonaws.com/bucket/module.zip"
HTTP archive"https://example.com/vpc.zip"
# Registry module with version pinning
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"
  azs  = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

Writing a Module

modules/
└── vpc/
    ├── main.tf        # resources
    ├── variables.tf   # inputs
    ├── outputs.tf     # exports
    └── versions.tf    # required_providers
# modules/vpc/variables.tf
variable "cidr_block" {
  type        = string
  description = "CIDR block for the VPC"
}

variable "environment" {
  type    = string
  default = "dev"
}
# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block = var.cidr_block
  tags       = { Environment = var.environment }
}

resource "aws_subnet" "public" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.cidr_block, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
}
# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

Module Meta-Arguments

module "app" {
  source = "./modules/app"
  count  = var.enable_app ? 1 : 0        # conditional module
}

module "region" {
  source   = "./modules/region"
  for_each = toset(["us-east-1", "us-west-2"])  # multiple instances

  region = each.key
}

module "app" {
  source     = "./modules/app"
  depends_on = [module.vpc]             # explicit dependency

  providers = {                          # pass aliased provider
    aws = aws.west
  }
}

Passing Providers to Modules

# Root config
provider "aws" { region = "us-east-1" }
provider "aws" { alias = "west"; region = "us-west-2" }

module "west_resources" {
  source = "./modules/app"

  providers = {
    aws = aws.west
  }
}
# modules/app/versions.tf — declare expected provider
terraform {
  required_providers {
    aws = { source = "hashicorp/aws" }
  }
}

Module Outputs in for_each

module "bucket" {
  for_each = toset(["logs", "backups"])
  source   = "./modules/s3-bucket"
  name     = each.key
}

output "bucket_arns" {
  value = { for k, m in module.bucket : k => m.bucket_arn }
}

Module Commands

terraform init              # downloads/updates all modules
terraform get               # download modules only (no provider init)
terraform get -update       # re-download modules (pick up changes)

Module Versioning Best Practices

  • Pin Registry modules with version = "~> X.Y" (pessimistic constraint)
  • Pin Git modules with ?ref=v1.2.3 (tag), not branch names
  • Run terraform init -upgrade intentionally when you want to update