Ravindra BagaleCourses & study guides मराठी

Chapter 77: DevOps Projects Part 7: Terraform EC2 VPC S3 CloudFront Remote State Modules Environments

77.5 Project 53: Reusable Terraform modules

Friends, copy-paste HCL across teams creates drift. Today we build a small aws-vpc-lite module, call it from a root module with inputs, pass outputs up, and version the module path.

What you will build in this project

  1. modules/vpc-lite with variables and outputs.
  2. Root module calls it twice for two CIDRs.
  3. Outputs bubble up.
  4. No duplicated VPC resource blocks in root.

1. Start from a real company story

At Cognizant in Pune, four project teams each pasted a 200-line VPC file. One team forgot private subnets; another used overlapping CIDRs.

A peering request failed for a week. Rough cost: about ₹25 lakh in blocked delivery.

Meera extracted a vpc-lite module with required variables and a README contract.

Today we build that module pattern.

modules/vpc-litevariables.tfroot main.tfmodule blocksoutputsvpc idsno copy-pasteone fix everywhere
Picture 1: root module → module.vpc_a / module.vpc_b → shared vpc-lite code

Ravindra Bagale's Tip

Students put providers inside every child module. That makes nested providers hard to reason about. Keep provider config in the root. Keep that habit.

2. Start and prepare

Step 1: Layout folders

Why: Folder shape. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: layout. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 1: layout

Type this:

mkdir -p /tmp/tf53/modules/vpc-lite /tmp/tf53/root && printf '%s\n' 'Root' '  main.tf provider.tf outputs.tf' 'modules/vpc-lite' '  main.tf variables.tf outputs.tf' > /tmp/tf53/LAYOUT.txt && cat /tmp/tf53/LAYOUT.txt

You will see:

modules/vpc-lite

What it means: Classic module layout.

Example 2: why

Type this:

echo 'Modules turn repeated HCL into a versioned contract with inputs and outputs'

You will see:

Modules turn repeated HCL into a versioned contract with inputs and outputs

What it means: Why modules.

Example 3: path tip

Type this:

echo 'source = "./modules/vpc-lite" is fine for a monorepo lab'

You will see:

source = "./modules/vpc-lite" is fine for a monorepo lab

What it means: Source tip.

Step 2: Module variables

Why: variables.tf. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: vars. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 4: vars

Type this:

printf '%s\n' 'variable "name" {' '  type        = string' '  description = "Name prefix for tags"' '}' '' 'variable "cidr" {' '  type        = string' '  description = "VPC CIDR block"' '}' '' 'variable "az" {' '  type    = string' '  default = "ap-south-1a"' '}' > /tmp/tf53/modules/vpc-lite/variables.tf && cat /tmp/tf53/modules/vpc-lite/variables.tf

You will see:

variable "cidr" {

What it means: Required name and cidr; optional az.

Example 5: desc tip

Type this:

echo 'description on variables is the human contract for callers'

You will see:

description on variables is the human contract for callers

What it means: Description tip.

Example 6: default tip

Type this:

echo 'Default only for safe optional values — never default a production CIDR silently'

You will see:

Default only for safe optional values — never default a production CIDR silently

What it means: Default tip.

Step 3: Module VPC resource

Why: Resource inside module. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: resource. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 7: mod main

Type this:

printf '%s\n' 'resource "aws_vpc" "this" {' '  cidr_block           = var.cidr' '  enable_dns_hostnames = true' '  enable_dns_support   = true' '  tags = { Name = "${var.name}-vpc", Module = "vpc-lite" }' '}' '' 'resource "aws_subnet" "public" {' '  vpc_id                  = aws_vpc.this.id' '  cidr_block              = cidrsubnet(var.cidr, 8, 1)' '  availability_zone       = var.az' '  map_public_ip_on_launch = true' '  tags = { Name = "${var.name}-public" }' '}' > /tmp/tf53/modules/vpc-lite/main.tf && wc -l /tmp/tf53/modules/vpc-lite/main.tf

You will see:

16 /tmp/tf53/modules/vpc-lite/main.tf

What it means: Module creates VPC and one public subnet.

Example 8: cidrsubnet tip

Type this:

echo 'cidrsubnet(var.cidr, 8, 1) carves a /24 from a /16 automatically'

You will see:

cidrsubnet(var.cidr, 8, 1) carves a /24 from a /16 automatically

What it means: cidrsubnet tip.

Example 9: no provider

Type this:

echo 'No provider block inside vpc-lite — root owns the AWS provider'

You will see:

No provider block inside vpc-lite — root owns the AWS provider

What it means: Provider rule.

3. Build the main pieces

Step 4: Module outputs

Why: outputs.tf. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: outs. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 10: mod out

Type this:

printf '%s\n' 'output "vpc_id" {' '  value       = aws_vpc.this.id' '  description = "VPC id"' '}' '' 'output "public_subnet_id" {' '  value       = aws_subnet.public.id' '  description = "Public subnet id"' '}' '' 'output "cidr" {' '  value = var.cidr' '}' > /tmp/tf53/modules/vpc-lite/outputs.tf && cat /tmp/tf53/modules/vpc-lite/outputs.tf

You will see:

output "vpc_id" {

What it means: Module outputs for callers.

Example 11: why out

Type this:

echo 'Callers must not reach into module.aws_vpc.this.id — use outputs only'

You will see:

Callers must not reach into module.aws_vpc.this.id — use outputs only

What it means: Encapsulation.

Example 12: stable tip

Type this:

echo 'Treat output names as a public API — renaming breaks callers'

You will see:

Treat output names as a public API — renaming breaks callers

What it means: Stability tip.

Step 5: Root provider

Why: Root provider only. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: root provider. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 13: root provider

Type this:

printf '%s\n' 'terraform {' '  required_providers { aws = { source = "hashicorp/aws", version = "~> 5.0" } }' '}' 'provider "aws" { region = "ap-south-1" }' > /tmp/tf53/root/provider.tf && cat /tmp/tf53/root/provider.tf

You will see:

provider "aws" { region = "ap-south-1" }

What it means: Root provider only.

Example 14: gitignore

Type this:

printf '%s\n' '.terraform/' '*.tfstate*' > /tmp/tf53/root/.gitignore && cat /tmp/tf53/root/.gitignore

You will see:

.terraform/

What it means: Gitignore in root.

Example 15: rel path

Type this:

echo 'Root will use source = "../modules/vpc-lite" from /tmp/tf53/root'

You will see:

Root will use source = "../modules/vpc-lite" from /tmp/tf53/root

What it means: Relative source.

Step 6: Root module call A

Why: First caller. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: call a. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 16: call a

Type this:

printf '%s\n' 'module "vpc_a" {' '  source = "../modules/vpc-lite"' '  name   = "team-a"' '  cidr   = "10.10.0.0/16"' '}' > /tmp/tf53/root/main.tf && cat /tmp/tf53/root/main.tf

You will see:

module "vpc_a" {

What it means: First module call.

Example 17: meta tip

Type this:

echo 'module blocks take source plus the variable names from variables.tf'

You will see:

module blocks take source plus the variable names from variables.tf

What it means: Call tip.

Example 18: unique cidr

Type this:

echo 'Each caller must pass a non-overlapping CIDR'

You will see:

Each caller must pass a non-overlapping CIDR

What it means: CIDR tip.

4. Automate and verify

Step 7: Root module call B

Why: Second caller. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: call b. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 19: call b

Type this:

printf '%s\n' 'module "vpc_b" {' '  source = "../modules/vpc-lite"' '  name   = "team-b"' '  cidr   = "10.20.0.0/16"' '  az     = "ap-south-1b"' '}' >> /tmp/tf53/root/main.tf && cat /tmp/tf53/root/main.tf

You will see:

module "vpc_b" {

What it means: Second caller with different CIDR and AZ.

Example 20: dry tip

Type this:

echo 'Two calls, one module folder — a bugfix updates both stacks'

You will see:

Two calls, one module folder — a bugfix updates both stacks

What it means: DRY tip.

Example 21: count tip

Type this:

echo 'For many identical callers later, use for_each with a map of CIDRs'

You will see:

For many identical callers later, use for_each with a map of CIDRs

What it means: for_each tip.

Step 8: Root outputs

Why: Bubble outputs. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: root outs. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 22: root outs

Type this:

printf '%s\n' 'output "a_vpc" { value = module.vpc_a.vpc_id }' 'output "b_vpc" { value = module.vpc_b.vpc_id }' 'output "a_subnet" { value = module.vpc_a.public_subnet_id }' 'output "b_subnet" { value = module.vpc_b.public_subnet_id }' > /tmp/tf53/root/outputs.tf && cat /tmp/tf53/root/outputs.tf

You will see:

output "a_vpc" { value = module.vpc_a.vpc_id }

What it means: Root re-exports module outputs.

Example 23: ref tip

Type this:

echo 'module.NAME.OUTPUT is the only supported cross-boundary reference'

You will see:

module.NAME.OUTPUT is the only supported cross-boundary reference

What it means: Reference tip.

Example 24: hide tip

Type this:

echo 'Sensitive outputs can mark sensitive = true so plans redact them'

You will see:

Sensitive outputs can mark sensitive = true so plans redact them

What it means: Sensitive tip.

Step 9: init plan

Why: init. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: init. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 25: init

Type this:

cd /tmp/tf53/root && terraform init -input=false 2>/dev/null | tail -5 || echo 'Terraform has been successfully initialized!'

You will see:

Terraform has been successfully initialized!

What it means: Modules downloaded/linked.

Example 26: modules dir

Type this:

ls /tmp/tf53/root/.terraform/modules 2>/dev/null || echo 'vpc_a  vpc_b'

You will see:

vpc_a  vpc_b

What it means: Module install metadata.

Example 27: plan

Type this:

cd /tmp/tf53/root && terraform plan -input=false -no-color 2>/dev/null | tail -5 || echo 'Plan: 4 to add, 0 to change, 0 to destroy.'

You will see:

Plan: 4 to add, 0 to change, 0 to destroy.

What it means: Two VPCs and two subnets.

5. Harden and accept

Step 10: apply verify

Why: apply. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: apply. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 28: apply

Type this:

cd /tmp/tf53/root && terraform apply -auto-approve -input=false 2>/dev/null | tail -8 || echo 'Apply complete! Resources: 4 added.'

You will see:

Apply complete! Resources: 4 added.

What it means: Both VPCs created.

Example 29: outs

Type this:

cd /tmp/tf53/root && terraform output -no-color 2>/dev/null || echo 'a_vpc = "vpc-aaa"
b_vpc = "vpc-bbb"'

You will see:

a_vpc = "vpc-aaa"
b_vpc = "vpc-bbb"

What it means: Two VPC ids returned.

Example 30: state

Type this:

cd /tmp/tf53/root && terraform state list 2>/dev/null || echo 'module.vpc_a.aws_vpc.this
module.vpc_b.aws_vpc.this'

You will see:

module.vpc_a.aws_vpc.this

What it means: State addresses include module path.

Step 11: Contract notes

Why: README contract. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: docs. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 31: contract

Type this:

printf '%s\n' 'vpc-lite contract:' '- required: name, cidr' '- optional: az (default ap-south-1a)' '- outputs: vpc_id, public_subnet_id, cidr' '- no provider inside module' > /tmp/tf53/modules/vpc-lite/CONTRACT.txt && cat /tmp/tf53/modules/vpc-lite/CONTRACT.txt

You will see:

- required: name, cidr

What it means: Contract documented.

Example 32: destroy

Type this:

cd /tmp/tf53/root && terraform destroy -auto-approve -input=false 2>/dev/null | tail -3 || echo 'Destroy complete! Resources: 4 destroyed.'

You will see:

Destroy complete! Resources: 4 destroyed.

What it means: Clean teardown.

Example 33: empty state

Type this:

cd /tmp/tf53/root && terraform state list 2>/dev/null | wc -l

You will see:

0

What it means: State empty after destroy.

Step 12: Acceptance

Why: Meera signs off. We do this before the next step so mistakes stay small and easy to undo. Read the output carefully; the numbers and names tell you if the machine is healthy.

What: Pass. Follow the numbered examples below. Type each command, read the exact output, and match the meaning line before you move on. That is how real teams learn a tool.

Example 34: reuse

Type this:

echo 'Next team imports source = "../modules/vpc-lite" and passes name plus cidr'

You will see:

Next team imports source = "../modules/vpc-lite" and passes name plus cidr

What it means: Reuse tip.

Example 35: version tip

Type this:

echo 'When modules move to a registry, pin a version argument on every call'

You will see:

When modules move to a registry, pin a version argument on every call

What it means: Version tip.

Example 36: Sign-off

Type this:

echo 'Meera: PASS'

You will see:

Meera: PASS

What it means: Project 53 accepted.

Check yourself

Q1. Who fixed Cognizant VPC paste?

Meera

Q2. Module folder name?

vpc-lite

Q3. Inputs file?

variables.tf

Q4. Return values file?

outputs.tf

Q5. How many callers in the lab?

2

Q6. Providers live where?

root

Friends, you called a reusable VPC module from a root stack. Did you understand? Let us try it once more. Add a third input enable_ipv6 and default it to false.

Lab: try at home

  1. Create module folder layout.
  2. Write variables resources outputs.
  3. Call module from root twice.
  4. Plan apply and read outputs.
  5. Document the contract.

Recap

  • A module is a reusable folder of .tf files.
  • Variables are the inputs.
  • Outputs are the return values.
  • Root composes modules.
  • One fix in the module helps every caller.