Goal
- Use multiple AWS regions (
us-east-1andus-west-1) - Use provider aliases
- Use modules
- Create EC2 in each region
- Understand who owns providers (root vs module)
1οΈβ£ Project Folder Structure (VERY IMPORTANT)
π βFirst create folders exactly like this.β
terraform-multi-provider/
β
βββ main.tf
βββ providers.tf
βββ variables.tf
βββ outputs.tf
β
βββ modules/
β βββ ec2/
β βββ main.tf
β βββ variables.tf
β βββ outputs.tf
Students should not guess paths.
This structure removes confusion.
2οΈβ£ Root Level β providers.tf (Multiple Providers)
π terraform-multi-provider/providers.tf
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "west"
region = "us-west-1"
}
Explain to students
- Default provider β
us-east-1 - Aliased provider β
aws.west - Terraform does not auto-switch regions
- Aliases are mandatory for multi-region
3οΈβ£ Root Level β main.tf (Calling Modules)
π terraform-multi-provider/main.tf
module "ec2_east" {
source = "./modules/ec2"
instance_name = "east-instance"
}
module "ec2_west" {
source = "./modules/ec2"
providers = {
aws = aws.west
}
instance_name = "west-instance"
}
Explain clearly
| Module | Region | Why |
|---|---|---|
ec2_east |
us-east-1 | Uses default provider |
ec2_west |
us-west-1 | Uses aliased provider |
π providers block tells Terraform which provider to inject into the module
4οΈβ£ Root Level β variables.tf
π terraform-multi-provider/variables.tf
# Empty for now (kept for scalability)
Explain:
- Empty now
- Keeps structure future-proof
- Matches real production repos
5οΈβ£ Root Level β outputs.tf
π terraform-multi-provider/outputs.tf
output "east_instance_id" {
value = module.ec2_east.instance_id
}
output "west_instance_id" {
value = module.ec2_west.instance_id
}
Explain:
- Outputs prove both regions worked
- Used in CI/CD and remote state later
6οΈβ£ Module β variables.tf
π terraform-multi-provider/modules/ec2/variables.tf
variable "instance_name" {
description = "Name of EC2 instance"
type = string
}
Explain:
- Modules never hardcode names
- Everything is passed from root
7οΈβ£ Module β main.tf (NO PROVIDER BLOCK HERE)
π terraform-multi-provider/modules/ec2/main.tf
resource "aws_instance" "this" {
ami = "ami-0fc5d935ebf8bc3bc" # Amazon Linux 2 (example)
instance_type = "t2.micro"
tags = {
Name = var.instance_name
}
}
π¨ Important rule (exam + interview)
β Do NOT define provider inside module
β
Provider is injected from root
8οΈβ£ Module β outputs.tf
π terraform-multi-provider/modules/ec2/outputs.tf
output "instance_id" {
value = aws_instance.this.id
}
9οΈβ£ How Students Run This Project
terraform init
terraform plan
terraform apply
Expected result:
- 1 EC2 in us-east-1
- 1 EC2 in us-west-1
10οΈβ£ Interview Explanation (MEMORIZE THIS)
Question:
Why did you use multiple providers with modules?
Answer:
In production, teams deploy infrastructure across multiple regions for availability and compliance. Terraform requires provider aliases to manage multiple regions. I defined providers in the root module and injected them into reusable modules to keep the module region-agnostic and production-ready.
11οΈβ£ Certification Key Points (Terraform Associate)
β Provider aliases
β Module provider injection
β Region-agnostic modules
β Clean folder structure
β Reusability
THIS ONE PROJECT
- Real production layout
- How Terraform resolves providers
- Why modules should stay generic
- How multi-region infra is built
- How to explain it in interviews
PROJECT: Publishable Terraform Module β AWS EC2
Project Goal
Build a reusable, production-ready EC2 module and publish it to the Terraform Registry, following all official requirements.
What will learn:
- Module structure
- Registry naming rules
- Variables & outputs
- Versioning with Git tags
- Registry publishing flow
- Interview-ready explanation
1οΈβ£ GitHub Repository Name (MANDATORY)
terraform-aws-ec2
π¨ If the name is wrong β Terraform Registry will NOT detect it.
2οΈβ£ Project Folder Structure (FIRST THING STUDENTS CREATE)
terraform-aws-ec2/
β
βββ main.tf
βββ variables.tf
βββ outputs.tf
βββ versions.tf
βββ README.md
π No subfolders.
π Everything at root (required by Registry).
3οΈβ£ versions.tf (Provider + Terraform Version)
π versions.tf
terraform {
required_version = ">= 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
Why this matters:
- Prevents breaking changes
- Required in real production modules
- Exam question topic
4οΈβ£ variables.tf (NO HARDCODED VALUES)
π variables.tf
variable "instance_name" {
description = "Name of the EC2 instance"
type = string
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "ami_id" {
description = "AMI ID for EC2"
type = string
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
Teaching point:
- Modules must be flexible
- Everything configurable
- Defaults allowed, not required
5οΈβ£ main.tf (Core Module Logic)
π main.tf
resource "aws_instance" "this" {
ami = var.ami_id
instance_type = var.instance_type
tags = merge(
{
Name = var.instance_name
},
var.tags
)
}
π¨ IMPORTANT:
- β No provider block here
- β No region here
- β Region comes from root module
6οΈβ£ outputs.tf (Expose Important Values)
π outputs.tf
output "instance_id" {
description = "EC2 instance ID"
value = aws_instance.this.id
}
output "public_ip" {
description = "Public IP address"
value = aws_instance.this.public_ip
}
Why outputs matter:
- Used by other modules
- Used by CI/CD
- Required for professional modules
7οΈβ£ README.md (REGISTRY READS THIS)
π README.md
# terraform-aws-ec2
Reusable Terraform module for creating an AWS EC2 instance.
## Usage
```
hcl
provider "aws" {
region = "us-east-1"
}
module "ec2" {
source = "username/ec2/aws"
version = "1.0.0"
instance_name = "demo-ec2"
instance_type = "t2.micro"
ami_id = "ami-0fc5d935ebf8bc3bc"
tags = {
Environment = "dev"
}
}
Inputs
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| instance_name | EC2 name | string | n/a | yes |
| instance_type | EC2 type | string | t2.micro | no |
| ami_id | AMI ID | string | n/a | yes |
| tags | Extra tags | map(string) | {} | no |
Outputs
| Name | Description |
|---|---|
| instance_id | EC2 ID |
| public_ip | Public IP |
`
π¨ No README = module rejected
---
## 8οΈβ£ How Students Test Module (LOCAL)
Create a **separate test folder** (not published):
```bash
ec2-test/
β
βββ main.tf
````
```hcl
provider "aws" {
region = "us-east-1"
}
module "test_ec2" {
source = "../terraform-aws-ec2"
instance_name = "student-test"
ami_id = "ami-0fc5d935ebf8bc3bc"
}
```
Run:
```bash
terraform init
terraform apply
```
---
## 9οΈβ£ Git Versioning (CRITICAL FOR REGISTRY)
```bash
git init
git add .
git commit -m "Initial EC2 module"
git tag v1.0.0
git push origin main
git push origin v1.0.0
```
Terraform Registry uses:
```hcl
version = "1.0.0"
```
---
## π Publishing to Terraform Registry
1. Go to Terraform Registry
2. Sign in with GitHub
3. Click **Publish Module**
4. Select `terraform-aws-ec2`
5. Done
---
## 1οΈβ£1οΈβ£ Interview Explanation (MEMORIZE)
**Question:**
How do you publish a Terraform module?
**Answer:**
> I create a public GitHub repository following the `terraform-<provider>-<name>` naming convention. The module contains `main.tf`, `variables.tf`, `outputs.tf`, a `versions.tf`, and a well-documented README. Versions are managed using Git tags, and Terraform Registry automatically picks up the module.
---
## 1οΈβ£2οΈβ£ Why This Is a STRONG Project
β Registry compliant
β Real production pattern
β Certification aligned
β Interview-ready
β Student-friendly
β Reusable in real jobs
Top comments (0)