# 5) Build & Reuse a Simple Module (25 min) In this section, you’ll turn repeated EC2 logic into a **reusable Terraform module**. Modules make your code **DRY** (Don’t Repeat Yourself), easier to test, and simpler to use across teams and environments. ## What is a module? A **module** is just a folder that contains Terraform configuration files (`.tf`). Your **root module** (the folder where you run the CLI) can **call** other modules from: - A local path (`./modules/ec2-instance`) - A Git repo (`git::https://...`), or - A registry (`registry.terraform.io`) Here we’ll use a **local** module. ## Step 1: Create the project & module folder ```bash mkdir -p ~/terraform-modules-lab/modules/ec2-instance cd ~/terraform-modules-lab ``` - Creates a workspace with a nested folder `modules/ec2-instance` that will contain reusable EC2 code. Your tree will look like: ``` terraform-modules-lab/ main.tf # root (calls the module) modules/ ec2-instance/ variables.tf main.tf # module implementation outputs.tf ``` ## Step 2: Define module inputs (variables) Create **modules/ec2-instance/variables.tf**: ```hcl variable "instance_type" { description = "Type of EC2 instance"; default = "t2.micro" } variable "instance_name" { description = "Tag name for instance" } variable "instance_count" { description = "Number of EC2"; default = 1 } ``` - These are **inputs** the module expects. - `default` makes inputs optional (callers can override). - You can add stronger typing & validation for safety: ```hcl variable "instance_type" { type = string description = "EC2 size" default = "t2.micro" validation { condition = can(regex("^t[23]\.", var.instance_type)) error_message = "Use a t2.* or t3.* instance for this lab." } } ``` ## Step 3: Implement the module logic Create **modules/ec2-instance/main.tf**: ```hcl resource "aws_instance" "this" { count = var.instance_count ami = "ami-0e6329e222e662a52" instance_type = var.instance_type tags = { Name = "${var.instance_name}-${count.index}" } } ``` - Uses `count` to create **N instances** with a single block. - `tags.Name` includes the `count.index` suffix so each instance has a unique name (e.g., `App-Server-0`, `App-Server-1`). > **Note on AMIs:** AMI IDs are **region-specific**. We use an Amazon Linux 2 AMI for **ap-south-1 (Mumbai)**. If you switch regions, update this AMI or fetch it dynamically (e.g., with a data source). ## Step 4: Expose useful outputs Create **modules/ec2-instance/outputs.tf**: ```hcl output "instance_ids" { value = [for i in aws_instance.this : i.id] } output "public_ips" { value = [for i in aws_instance.this : i.public_ip] } ``` - Makes it easy for callers to **consume** important info (IDs, IPs). - The `for` expression collects values from the resource instances created via `count`. ## Step 5: Call the module from the root Create the **root** `main.tf` at `~/terraform-modules-lab/main.tf`: ```hcl provider "aws" { region = "ap-south-1" } module "ec2_instance" { source = "./modules/ec2-instance" instance_type = "t2.micro" instance_name = "App-Server" instance_count = 2 } ``` - `source` points to the **local path** of your module folder. - Inputs (`instance_type`, `instance_name`, `instance_count`) are set by the **caller** (the root module). - You can define **multiple** module blocks to create different groups of instances, or use `.tfvars` files for environments. ## Step 6: Initialize and apply ```bash terraform init terraform apply -auto-approve ``` - `terraform init` downloads providers and **fetches module sources** (local/Git/registry). - `terraform apply` creates **2 EC2 instances** named `App-Server-0` and `App-Server-1` in **ap-south-1**. After the apply, view outputs: ```bash terraform output ``` You should see lists for `instance_ids` and `public_ips`. ## How to reuse this module elsewhere - Copy the `modules/ec2-instance` folder into any Terraform project and call it with `source = "./modules/ec2-instance"`. - Or publish the module to a **Git repo** and reference with `source = "git::https://github.com/yourorg/yourrepo//modules/ec2-instance?ref=v1.0.0"`. - Standardize inputs/outputs so teams can use it without reading internals. ## Best practices - **Version control your modules** (Git tags) to avoid breaking changes. - Add **README.md** inside the module with usage examples and input/output docs. - Prefer **types & validation** for variables. - Keep AMIs **parametrized** or discover them via a `data "aws_ami"` query to avoid hard-coding. - If multiple teams use the same module, consider a **private Terraform registry** or a Git monorepo with clear versioning. ## Cleanup When done (to avoid charges): ```bash terraform destroy -auto-approve ``` ## Summary You built a **reusable EC2 module**, exposed clean **inputs/outputs**, and consumed it from the **root**. This pattern scales to VPCs, RDS, ALBs, and more—compose modules like building blocks to create reliable infrastructure at speed.