By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
You’re building a Terraform project to deploy a multi-environment AWS infrastructure (dev, staging, prod). Each environment needs: - A VPC with different CIDR blocks - EC2 instances with varying sizes - RDS databases with different storage and instance types
Without modules, you’d copy-paste the same main.tf three times, manually changing values. With modules, you define the infrastructure once and parameterize it—passing different variables for each environment.
main.tf
Why this matters in production:- DRY (Don’t Repeat Yourself): One change fixes all environments.- Consistency: No more "works in dev but fails in prod" because of a typo.- Security: Sensitive values (like DB passwords) can be passed securely via variables.- Scalability: Add a new environment (e.g., qa) in minutes, not hours.
qa
Real-world scenario:You inherit a Terraform repo where someone hardcoded instance_type = "t2.micro" in 15 places. Your boss says, "We need to upgrade all prod instances to t3.medium." Without modules, you’d manually edit 15 files. With modules, you change one variable.
instance_type = "t2.micro"
t3.medium
module
source = "git::https://github.com/org/vpc-module?ref=v1.2.0"
variable
validation { condition = var.instance_type == "t3.micro" || var.instance_type == "t3.small" }
output
locals
local.subnet_cidr = "${var.vpc_cidr}.0.0/24"
terraform.tfvars
instance_type = "t3.medium"
*.tfvars
.gitignore
-var
-var-file
terraform apply -var="instance_type=t3.large"
sensitive = true
depends_on
terraform --version
aws sts get-caller-identity
Deploy a VPC with public/private subnets using a module, passing variables for: - CIDR block - Subnet sizes - Environment name (for tagging)
mkdir -p terraform-modules/vpc cd terraform-modules/vpc touch main.tf variables.tf outputs.tf
# terraform-modules/vpc/main.tf resource "aws_vpc" "this" { cidr_block = var.vpc_cidr enable_dns_support = true enable_dns_hostnames = true tags = { Name = "${var.environment}-vpc" Environment = var.environment } } resource "aws_subnet" "public" { count = length(var.public_subnet_cidrs) vpc_id = aws_vpc.this.id cidr_block = var.public_subnet_cidrs[count.index] availability_zone = data.aws_availability_zones.available.names[count.index] map_public_ip_on_launch = true tags = { Name = "${var.environment}-public-subnet-${count.index}" Environment = var.environment } } resource "aws_subnet" "private" { count = length(var.private_subnet_cidrs) vpc_id = aws_vpc.this.id cidr_block = var.private_subnet_cidrs[count.index] availability_zone = data.aws_availability_zones.available.names[count.index] tags = { Name = "${var.environment}-private-subnet-${count.index}" Environment = var.environment } } data "aws_availability_zones" "available" { state = "available" }
variables.tf
# terraform-modules/vpc/variables.tf variable "vpc_cidr" { description = "CIDR block for the VPC" type = string default = "10.0.0.0/16" } variable "public_subnet_cidrs" { description = "List of CIDR blocks for public subnets" type = list(string) default = ["10.0.1.0/24", "10.0.2.0/24"] } variable "private_subnet_cidrs" { description = "List of CIDR blocks for private subnets" type = list(string) default = ["10.0.3.0/24", "10.0.4.0/24"] } variable "environment" { description = "Environment name (e.g., dev, prod)" type = string default = "dev" }
outputs.tf
# terraform-modules/vpc/outputs.tf output "vpc_id" { description = "ID of the created VPC" value = aws_vpc.this.id } output "public_subnet_ids" { description = "IDs of the public subnets" value = aws_subnet.public[*].id } output "private_subnet_ids" { description = "IDs of the private subnets" value = aws_subnet.private[*].id }
cd ../..mkdir -p environments/dev cd environments/dev touch main.tf terraform.tfvars
# environments/dev/main.tf terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "us-east-1" } module "vpc" { source = "../../terraform-modules/vpc" vpc_cidr = var.vpc_cidr public_subnet_cidrs = var.public_subnet_cidrs private_subnet_cidrs = var.private_subnet_cidrs environment = var.environment }
# environments/dev/terraform.tfvars vpc_cidr = "10.1.0.0/16" public_subnet_cidrs = ["10.1.1.0/24", "10.1.2.0/24"] private_subnet_cidrs = ["10.1.3.0/24", "10.1.4.0/24"] environment = "dev"
terraform init terraform plan terraform apply -auto-approve
terraform output
Expected Output:
vpc_id = "vpc-1234567890abcdef0" public_subnet_ids = [ "subnet-1234567890abcdef0", "subnet-0987654321fedcba0", ] private_subnet_ids = [ "subnet-abcdef12345678900", "subnet-fedcba09876543210", ]
Let’s say you want to deploy an EC2 instance in a public subnet. You can reference the VPC module’s outputs:
# environments/dev/main.tf (add this) module "ec2" { source = "../../terraform-modules/ec2" subnet_id = module.vpc.public_subnet_ids[0] # Use first public subnet instance_type = "t3.micro" environment = var.environment }
hcl variable "instance_type" { type = string validation { condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type) error_message = "Instance type must be t3.micro, t3.small, or t3.medium." } }
count
for_each
hcl resource "aws_instance" "example" { count = var.environment == "prod" ? 2 : 1 instance_type = var.instance_type }
hcl tags = { Environment = var.environment CostCenter = "12345" }
hcl locals { subnet_cidr = "${var.vpc_cidr}.0.0/24" }
aws_instance.web_server
aws_instance.web_server_${var.environment}
hcl output "alb_dns_name" { value = aws_lb.this.dns_name }
terraform state list
bash terraform state list
variables
ref=v1.2.0
source
Error: Incorrect attribute value type
type = string
list
map
module "vpc" { vpc_cidr = "10.0.0.0/16" }
❌ Wrong: Hardcoding in the module.
"How do you access a module’s output?"
module.vpc.vpc_id
❌ Wrong: aws_vpc.this.id (only works inside the module).
aws_vpc.this.id
"What’s the difference between variable and locals?"
terraform.tfvars = sets variable values.
count vs. for_each
aws_subnet.public[0]
aws_subnet.public["us-east-1a"]
Challenge:Create a module that deploys an S3 bucket with: - A variable for the bucket name (default: my-bucket-${random_id.suffix.hex}) - A variable for versioning (default: false) - An output for the bucket ARN
my-bucket-${random_id.suffix.hex}
versioning
false
Solution:
# modules/s3/main.tf resource "random_id" "suffix" { byte_length = 4 } resource "aws_s3_bucket" "this" { bucket = "${var.bucket_name}-${random_id.suffix.hex}" versioning { enabled = var.versioning } } # modules/s3/variables.tf variable "bucket_name" { type = string default = "my-bucket" } variable "versioning" { type = bool default = false } # modules/s3/outputs.tf output "bucket_arn" { value = aws_s3_bucket.this.arn }
Why it works:- random_id ensures unique bucket names.- versioning is configurable via a variable.- The ARN is exposed for other modules to use.
random_id
module "name" { ... }
variable "name" { ... }
type
description
output "name" { ... }
-json
terraform apply -var="key=value"
locals { ... }
depends_on = [resource]
Final Tip:
"Modules are like Lego blocks. Each block does one thing well. Combine them to build anything—just don’t glue them together permanently."
Now go build something! ?
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.