π Firewall Basics & AWS Security Groups
1. What is a Port? (Very Important Foundation)
Simple definition
A port is like a door on a server that allows a specific application to communicate.
- Server = House
- IP address = House address
- Port = Door number
- Application = Person inside the house
Each application listens on a specific port.
Common ports
| Service | Port | Purpose |
|---|---|---|
| SSH | 22 | Login to server |
| HTTP | 80 | Website |
| HTTPS | 443 | Secure website |
| FTP | 21 | File transfer |
2. Why Ports Exist (Real Example)
Suppose a server has:
-
SSH β Port
22 -
NGINX (Website) β Port
80 -
FTP β Port
21
All services are running on one IP address (example: 1.2.3.4), but ports tell the OS which application should receive the traffic.
Example:
http://1.2.3.4:80 β Website
ssh 1.2.3.4 β SSH (port 22 by default)
If ports didnβt exist, the server wouldnβt know which application should answer.
3. Checking Open Ports on a Server
netstat -ntlp
This shows:
- Which ports are open
- Which application is using the port
Example output meaning:
- Port
80β NGINX - Port
22β SSH - Port
21β FTP (vsftpd)
π Installing software usually automatically binds it to a port using its config file.
4. What is a Firewall?
High-level definition
A firewall is a security system that controls:
- β Who can connect to your server (inbound)
- β Where your server can connect to (outbound)
It works using rules.
5. Why Firewalls Are Needed
Your server may have multiple open ports, but you should not expose all of them.
Example:
- β Allow users to access the website (port 80)
- β Block users from accessing SSH (port 22)
Without firewall:
- Anyone could try to log in via SSH
- Huge security risk
6. How Firewall Logic Works (Mentally Picture This)
User β Firewall β Server β Application
Firewall checks rules before traffic reaches the server.
Sample rules:
- β Deny port 22
- β Allow port 80
Result:
- Website works
- SSH blocked from the internet
7. Firewall in AWS = Security Group
Very important AWS concept
AWS Security Group = Firewall
Official definition:
A security group acts as a virtual firewall for your EC2 instance, controlling inbound and outbound traffic.
8. AWS Security Group β Inbound Rules
Inbound rules control:
π Who can connect to your EC2
Example inbound rules:
| Port | Source | Meaning |
|---|---|---|
| 80 | 0.0.0.0/0 | Anyone can access website |
| 22 | Your IP only | Only you can SSH |
0.0.0.0/0 means:
β All IP addresses (entire internet)
9. What Happens If You Remove All Inbound Rules?
- No traffic allowed
- Website stops loading
- SSH stops working
This is exactly what happened in the demo when:
- βAllow all trafficβ rule was removed
- Website became inaccessible
10. Allowing Only the Website (Best Practice)
Correct setup for public website:
β
Inbound:
- Allow TCP
80from0.0.0.0/0
β Do NOT allow SSH from everyone
Result:
- Website works
- Server login protected
11. Outbound Rules (Often Forgotten but Important)
Outbound rules control:
π Where your server can connect
Examples:
- Can EC2 access the internet?
- Can EC2 talk only to a database?
- Can EC2 download updates?
By default:
β
AWS allows all outbound traffic
But security teams often restrict outbound traffic in production.
12. Firewalls Are the Same Everywhere (AWS, Azure, DigitalOcean)
Concept is identical across providers:
- AWS β Security Groups
- DigitalOcean β Firewall
- Azure β NSG (Network Security Group)
All use:
- Ports
- Allow/Deny rules
- Source & destination IPs
13. Key Takeaways (Very Important for Interviews)
β
Ports identify applications
β
Applications bind to ports
β
Firewalls control traffic
β
AWS firewall = Security Group
β
Inbound = who can connect to me
β
Outbound = where I can connect
β
Never open SSH (22) to the world
14. One-Line Interview Answer
Q: What is a firewall in AWS?
A:
A firewall in AWS is implemented using Security Groups, which control inbound and outbound traffic at the EC2 instance level based on port, protocol, and source/destination rules.
AWS Security Groups β Console + Terraform
Part 1: Creating a Security Group from AWS Console
1. Security Group = Firewall
In AWS:
- Firewall is called a Security Group
- Every EC2 must have a security group attached
When launching an EC2 instance, AWS always asks:
- Key pair
- Firewall (Security Group)
2. Default Security Group (Auto-created)
If you donβt create one manually:
- AWS creates a default security group
- It contains some default inbound & outbound rules
But in real projects:
- You always create your own security groups
3. Creating a Security Group from Scratch (Console)
Steps:
- Go to EC2 β Network & Security β Security Groups
- Click Create security group
- Example:
-
Name:
myfirst.sg - Description: Firewall
- VPC: Select your VPC
4. Inbound Rules (Who can connect TO your server)
Example rule:
- Type: SSH
- Port: 22
- Source:
0.0.0.0/0
β οΈ Important (Production Rule):
Never allow SSH (22) from
0.0.0.0/0in production
Allow only:
- Office IP
- VPN IP
5. Outbound Rules (Where server can connect TO)
Default outbound rule:
- Allow All
- Destination:
0.0.0.0/0
This allows:
- OS updates
- API calls
- Internet access
6. Attaching Security Group to EC2
Steps:
- EC2 β Instance β Actions β Security β Change security groups
- Remove default SG
- Attach
myfirst.sg
β Now firewall rules apply to this EC2
7. Why Ping Didnβt Work (ICMP Explained)
-
pinguses ICMP protocol - ICMP is not TCP
- Security groups block ICMP by default
ICMP rule was added to:
β Outbound
Correct is:
β
Inbound
Correct ICMP Rule
- Type: ICMP β Echo Request
- Source:
0.0.0.0/0
Now ping <EC2-IP> works.
Part 2: Creating Security Group Using Terraform
8. Architecture to Implement (Terraform)
We want:
- Security Group name:
terraform-firewall -
Inbound:
- Allow port 80 from
0.0.0.0/0
- Allow port 80 from
-
Outbound:
- Allow all traffic
9. Terraform Resource Types (Very Important)
There are two separate things:
1οΈβ£ Security Group (container)
resource "aws_security_group" "firewall" {
name = "terraform-firewall"
}
This creates:
β
Security group
β No rules yet
2οΈβ£ Security Group Rules (real firewall logic)
Terraform uses separate resources:
| Purpose | Terraform Resource |
|---|---|
| Inbound | aws_vpc_security_group_ingress_rule |
| Outbound | aws_vpc_security_group_egress_rule |
Ingress = inbound
Egress = outbound
10. Provider Configuration
provider "aws" {
region = "us-east-1"
}
11. Full Terraform Code (Clean Version)
πΉ firewall.tf
provider "aws" {
region = "us-east-1"
}
resource "aws_security_group" "firewall" {
name = "terraform-firewall"
description = "Managed from Terraform"
}
πΉ Inbound Rule β Allow HTTP
resource "aws_vpc_security_group_ingress_rule" "allow_http" {
security_group_id = aws_security_group.firewall.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
description = "Allow HTTP from Internet"
}
πΉ Outbound Rule β Allow All
resource "aws_vpc_security_group_egress_rule" "allow_all_outbound" {
security_group_id = aws_security_group.firewall.id
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
description = "Allow all outbound traffic"
}
12. What Does ip_protocol = "-1" Mean?
-1 means:
β
ALL protocols
β
ALL ports
Equivalent to:
- TCP
- UDP
- ICMP
- Any port
Used mainly in outbound rules.
13. Terraform Commands
terraform init
terraform plan
terraform apply -auto-approve
Expected result:
- Security group created
- Inbound rule: port 80
- Outbound rule: allow all
14. from_port & to_port (Port Range)
Single port:
from_port = 80
to_port = 80
Port range:
from_port = 80
to_port = 100
β Used when:
- Application uses multiple ports
- Load balancers
- Custom apps
15. security_group_id (Critical Concept)
security_group_id = aws_security_group.firewall.id
This means:
Attach this rule to that security group
Rules do not live alone
They always belong to one security group
16. Reassigning Rules to Another Security Group
If you change:
security_group_id = "sg-DEFAULT_ID"
Terraform will:
- β Remove rule from old SG
- β Add rule to new SG
Terraform output:
1 to add, 1 to destroy
β This is expected behavior
17. Final Mental Model (Very Important)
Security Group
βββ Ingress rules (Inbound)
βββ Egress rules (Outbound)
Terraform treats them as:
- Separate resources
- Connected using
security_group_id
18. Interview-Ready Summary
Q: How do you create AWS security groups using Terraform?
A:
First, create an
aws_security_groupresource. Then manage inbound and outbound rules separately usingaws_vpc_security_group_ingress_ruleandaws_vpc_security_group_egress_rule, linking them using the security group ID.
How to Deal with Terraform Documentation & Code Updates
When you work with Terraform long enough, you will notice:
- β Terraform documentation changes
- β Example code changes
- β New βrecommendedβ approaches appear
- β Old working code no longer matches docs
This does NOT mean old Terraform code is broken.
2. Security Group Example: Old vs New Approach
β New (Recommended) Approach
Terraform separates responsibilities:
- Security group
- Security group rules
aws_security_group
aws_vpc_security_group_ingress_rule
aws_vpc_security_group_egress_rule
This is:
- Cleaner
- More explicit
- Easier to manage at scale
- Current best practice
β Old (Legacy) Approach
Everything defined in one resource:
resource "aws_security_group" "example" {
ingress { ... }
egress { ... }
}
Important:
- β Still works
- β Supported by current providers
- β No longer emphasized in latest docs
3. Key Learning: Docs β Only Valid Way
Very important mindset shift:
Terraform documentation shows the recommended way,
not the only working way.
- HashiCorp adds new patterns
- They rarely remove working ones
- Backward compatibility is preserved
This is exactly what you saw:
- Provider version 5.38 still accepts old security group syntax
-
terraform applyworks without errors
4. Why This Matters in Real Jobs
In real companies:
- You will read Terraform code written 2β5 years ago
- It may not match current documentation
- The engineer who wrote it may be gone
- The code still works perfectly
π Your job is to understand it, not blindly rewrite it.
5. Terraform Versioning Strategy (Enterprise Reality)
Enterprises usually:
- Pin Terraform versions
- Pin provider versions
- Avoid frequent upgrades
Why?
- Stability
- Predictability
- Reduced risk
If Windows 10 works perfectly, why upgrade to Windows 11?
Same logic applies to Terraform.
6. How to Handle Documentation Changes Correctly
β Step 1: Identify Provider Version
Look at:
required_providers
or .terraform.lock.hcl
β Step 2: Switch Documentation Version
Terraform docs allow:
- Version selector (top-right)
- Older provider versions (e.g., 5.31.0)
Result:
- Examples match legacy code
- Less confusion
- Faster understanding
β Step 3: Compare Approaches (Donβt Panic)
If you see old code:
- β Check older docs
- β
Validate with
terraform plan - β Donβt rewrite unless required
7. HashiCorp Did This on Purpose (Good Design)
Why Terraform docs are good:
- Provider version selector
- Clear version history
- Stable APIs
- Gradual improvements, not breaking changes
Many tools do not offer this level of backward compatibility.
8. When Should You Actually Upgrade?
Upgrade Terraform / providers only when:
- New AWS feature required
- Security fix needed
- Bug fix required
- Organization-wide migration planned
Never upgrade just because:
β Docs changed
β New syntax looks cleaner
9. Course & Learning Reality (Important for Students)
Sometimes you will see:
- Older syntax in videos
- New syntax in docs
This does NOT mean:
- Video is wrong
- Code is broken
It just means:
- Docs moved forward
- Code stayed compatible
Now you know how to resolve that confusion.
10. Interview-Ready Summary (Very Important)
Q: How do you handle Terraform documentation changes?
Answer:
I check the provider version used in the codebase and refer to the corresponding documentation version. Terraform maintains backward compatibility, so older syntax usually continues to work even when newer recommended approaches are introduced.
Part 1: Elastic IP (EIP) in AWS Using Terraform
1. What is an Elastic IP?
At a high level:
- Elastic IP (EIP) is a static public IPv4 address in AWS.
-
It does not change even if:
- EC2 stops/starts
- EC2 gets recreated (if re-associated)
Why it exists
Normal EC2 public IPs:
- Change on stop/start
- Are temporary
Elastic IP:
- Fixed
- Can be reassigned to another instance
-
Useful for:
- Bastion hosts
- Production endpoints
- NAT instances
- Legacy systems needing static IPs
2. Elastic IP Lifecycle (Very Important)
- Create Elastic IP
- (Optional) Associate it with EC2
- Use the same public IP permanently
β οΈ Important cost note
AWS charges if:
- EIP is allocated but not attached to a running resource
Always delete unused EIPs.
3. Creating Elastic IP from AWS Console (Concept)
Steps:
- EC2 β Elastic IPs
- Allocate Elastic IP
- AWS immediately assigns a public IP
- You may associate it with an EC2 instance
No complexity here β Terraform works the same way.
4. Creating Elastic IP Using Terraform
Terraform Resource
resource "aws_eip" "example" {
domain = "vpc"
}
Thatβs it β Elastic IP created.
5. Terraform Code (Clean & Minimal)
eip.tf
provider "aws" {
region = "us-east-1"
}
resource "aws_eip" "example" {
domain = "vpc"
}
Key points:
-
instanceargument is optional - Without
instance, EIP is just allocated - Can be associated later
6. Terraform Commands
terraform plan
terraform apply -auto-approve
Result:
- Elastic IP created
- Visible in AWS console
- Stored in Terraform state
7. Terraform State & Elastic IP
In terraform.tfstate, youβll see:
"attributes": {
"public_ip": "100.xxx.xxx.xxx",
"id": "eipalloc-xxxx"
}
β You can get:
- Elastic IP address
- Allocation ID without visiting AWS Console
8. Cleanup (Very Important)
terraform destroy -auto-approve
Always:
- Destroy test EIPs
- Verify from AWS console
Part 2: Terraform Attributes (Critical Concept)
9. What Are Attributes in Terraform?
Definition (simple):
Attributes are values generated after a resource is created, and they are stored in the Terraform state file.
They represent:
- Resource IDs
- IP addresses
- DNS names
- ARNs
- Status information
10. Why Attributes Matter (Real Use Case)
Attributes allow:
- One resource to depend on another
- Passing values between resources
- Dynamic infrastructure wiring
Example:
-
Use EC2 public IP in:
- Outputs
- Load balancers
- Security groups
- DNS records
11. Attributes vs Arguments (Must Know Difference)
| Type | Meaning |
|---|---|
| Arguments | What you give Terraform |
| Attributes | What Terraform gives back |
Example:
ami = "ami-123"
β
Argument (input)
public_ip
β Attribute (output)
12. Where to Find Attributes?
In Terraform documentation:
- Scroll to Attribute Reference
- Listed per resource
Example:
aws_instanceaws_eip
13. Example: EC2 Attributes
After EC2 creation, state file contains:
| Attribute | Meaning |
|---|---|
| id | Instance ID |
| public_ip | Public IPv4 |
| private_ip | Internal IP |
| private_dns | Internal DNS |
| arn | AWS ARN |
These are auto-populated.
14. Reading Attributes from State File
Example from terraform.tfstate:
"public_ip": "3.xxx.xxx.xxx"
β
Matches AWS console
β
Terraform is the source of truth
15. Why Attributes Are Used Everywhere
Attributes are used for:
- Outputs
- Inter-resource connections
- Dynamic references
Example:
aws_instance.web.public_ip
Youβll use this constantly in real projects.
16. Very Important Mental Model
Terraform Code β Resource Created β Attributes Generated β Stored in tfstate
Terraform state is:
- Not just tracking existence
- Also storing real infrastructure values
17. Interview-Ready Summary
Q: What are attributes in Terraform?
Answer:
Attributes are values automatically generated after a resource is created, such as IDs, IP addresses, and ARNs. They are stored in the Terraform state file and used to connect resources together.
18. Key Takeaways (Remember This)
β
Elastic IP = static public IPv4
β
Terraform creates EIP with aws_eip
β
Unused EIPs cost money
β
Attributes are resource outputs
β
Attributes live in state file
β
Attributes are used for integration
Cross-Resource Attribute Reference in Terraform
1. Why This Concept Is Critical
In real Terraform projects:
- You never create isolated resources
- Resources depend on values from other resources
- Those values do not exist before apply
Example reality:
- EIP is created β public IP generated
- Security group must whitelist that exact IP
- You cannot hardcode it
π This problem is solved using cross-resource attribute references
2. The Problem We Are Solving
We want this workflow:
- Terraform creates an Elastic IP
- AWS assigns a public IP
- Terraform automatically:
- Reads that public IP
- Adds it to a Security Group rule
- Allows port 443 only from that IP
β
No manual copying
β
No hardcoding
β
Fully automated
3. Key Terraform Feature Used
Cross-Resource Attribute Reference
General syntax:
resource_type.resource_name.attribute
Example:
aws_eip.lb.public_ip
Meaning:
-
aws_eipβ resource type -
lbβ local resource name -
public_ipβ attribute generated after creation
4. Resources Involved
We need three resources:
- Elastic IP
- Security Group
- Security Group Ingress Rule
5. Step-by-Step Practical Code
β Provider
provider "aws" {
region = "us-east-1"
}
β Elastic IP Resource
resource "aws_eip" "lb" {
domain = "vpc"
}
After creation, Terraform knows:
aws_eip.lb.public_ip
β Security Group
resource "aws_security_group" "attribute_sg" {
name = "attribute-sg"
description = "Security group using cross-resource attributes"
}
β Security Group Ingress Rule (Important Part)
resource "aws_vpc_security_group_ingress_rule" "allow_https" {
security_group_id = aws_security_group.attribute_sg.id
from_port = 443
to_port = 443
ip_protocol = "tcp"
cidr_ipv4 = "${aws_eip.lb.public_ip}/32"
}
β This is the core learning
6. Why /32 Is Required (Very Important)
AWS security groups expect:
- CIDR notation
- NOT just an IP address
Examples:
- β
Single IP β
35.154.233.58/32 - β
Network β
10.0.0.0/24 - β
35.154.233.58β INVALID
Even if the IP is correct, AWS will reject it without CIDR.
7. Why Simple Reference Failed Before
This β does NOT work:
cidr_ipv4 = aws_eip.lb.public_ip
Why?
- That produces only an IP
- AWS expects CIDR
8. String Interpolation (Solution)
cidr_ipv4 = "${aws_eip.lb.public_ip}/32"
What Terraform does internally:
- Creates Elastic IP
- Gets
public_ip - Appends
/32 - Passes final CIDR to AWS
β This is string interpolation
9. Why security_group_id Also Uses Attributes
security_group_id = aws_security_group.attribute_sg.id
- Security group ID does not exist before apply
-
Terraform:
- Creates security group
- Reads its
idattribute - Injects it into rule resource
This creates an implicit dependency
10. Terraform Dependency Handling (Important)
Terraform creates resources in this order automatically:
- Elastic IP
- Security Group
- Security Group Rule
Because:
- Rule depends on EIP
public_ip - Rule depends on SG
id
No depends_on needed β Terraform is smart.
11. Why Terraform Apply Failed First Time
Earlier:
- EIP created β
- SG created β
- Rule failed β (invalid CIDR)
Result:
- Partial infrastructure
Terraform behavior:
- This is normal
- Terraform is idempotent
12. Best Practice (Very Important)
β Infrastructure should succeed in one apply
Good workflow:
- Destroy everything
- Fix code
- Apply again cleanly
terraform destroy -auto-approve
terraform apply -auto-approve
13. Where String Interpolation Is Required vs Not Required
| Case | Interpolation Needed |
|---|---|
security_group_id = aws_security_group.sg.id |
β No |
cidr_ipv4 = aws_eip.lb.public_ip |
β No |
cidr_ipv4 = aws_eip.lb.public_ip/32 |
β Invalid |
cidr_ipv4 = "${aws_eip.lb.public_ip}/32" |
β Yes |
Rule:
- If combining values + static text, use interpolation
14. Mental Model (Memorize This)
Resource A creates value β attribute
Resource B consumes attribute β reference
Terraform resolves dependency automatically
15. Interview-Ready Answer
Q: What is cross-resource attribute reference in Terraform?
Answer:
Cross-resource attribute reference allows one Terraform resource to use attributes generated by another resource, such as IDs or IP addresses, enabling dynamic dependencies and automated infrastructure wiring.
16. Key Takeaways
β
Attributes are outputs of resources
β
References use resource.type.name.attribute
β
Terraform computes dependencies automatically
β
CIDR requires /32 for single IPs
β
String interpolation is used when modifying values
β PART 1: Output Values in Terraform
1. What Are Output Values?
Simple definition:
Terraform output values allow you to print important information to your CLI after applying your infrastructure.
Examples of things you may want as output:
- EC2 public IP
- RDS endpoint
- Load balancer DNS
- EKS cluster name
- VPC ID
Outputs allow you to:
- Not open AWS Console
- Quickly copy the value
- Pass values to other Terraform projects
2. Why Do We Need Outputs?
Without outputs:
- You run
terraform apply - Terraform creates EC2 / EIP / LB
- You must go to AWS Console β find IP or DNS manually (time-consuming, annoying)
With outputs:
Terraform prints directly in CLI:
Outputs:
public_ip = "3.91.122.180"
You copy-paste instantly.
3. How to Create an Output?
Example resource
resource "aws_eip" "lb" {
domain = "vpc"
}
Output block
output "public_ip" {
value = aws_eip.lb.public_ip
}
Result
After terraform apply:
public_ip = "100.24.55.33"
4. Customize Output Strings
You can combine strings:
output "https_url" {
value = "https://${aws_eip.lb.public_ip}:8080"
}
CLI will show:
https_url = "https://100.24.55.33:8080"
This is VERY useful for:
- Testing services
- Giving URLs to developers
- Automation scripts
5. Output All Attributes of a Resource
output "everything_for_eip" {
value = aws_eip.lb
}
CLI will show:
- public_ip
- private_ip
- arn
- id
- domain
Very good for debugging.
6. Why Outputs Matter for Large Organizations
Outputs allow cross-project data exchange.
Example:
Project A:
Creates EIP + outputs:
public_ip = "100.24.55.33"
Project B:
Reads Project Aβs state file
β Uses that IP automatically.
This is used heavily in:
- Multi-team Terraform setups
- Multi-module systems
- Shared infrastructure components
β PART 2: Terraform Variables
1. Why Do We Need Variables?
Imagine you hardcode values inside 100 resources:
cidr_ipv4 = "34.87.112.9/32"
If the VPN IP changes tomorrow:
- You must update 100 places manually
- You WILL make mistakes
- Your apply will break
Variables solve this forever.
2. What Are Variables?
Simple definition:
Variables allow you to store important values in one place and reuse them anywhere in your Terraform code.
Example:
variable "vpn_ip" {
type = string
default = "34.87.112.9/32"
}
Use it:
cidr_ipv4 = var.vpn_ip
Now when VPN IP changes:
- Edit it once
- Everything updates automatically
3. Example Without Variables (Bad)
cidr_ipv4 = "34.87.112.9/32"
cidr_ipv4 = "34.87.112.9/32"
cidr_ipv4 = "34.87.112.9/32"
...
(100 more)
This is how people break production.
4. Example With Variables (Correct)
Define variables
variable "vpn_ip" {
default = "34.87.112.9/32"
}
variable "app_port" {
default = 8080
}
Use variables
cidr_ipv4 = var.vpn_ip
from_port = var.app_port
to_port = var.app_port
Now:
- 1 change = updated everywhere
- Zero manual edits
- Safer code
- Professional Terraform practice
5. Demo Behavior (Important)
If you change:
app_port = 22
Terraform plan will show:
~ from_port: 8080 β 22
~ to_port: 8080 β 22
Terraform automatically picks new values.
6. Benefits of Variables (Remember This)
β Centralized control
β No repeated static values
β No manual mistakes
β Faster code editing
β Cleaner Terraform code
β Essential for large organizations
β Final Summary (Interview Answer Style)
Q: What are Terraform output values?
Terraform outputs allow you to expose resource attributes (such as IPs or DNS) on the CLI and make them available to other Terraform configurations.
Q: What are Terraform variables?
Terraform variables let you define values in a central place instead of hardcoding them everywhere. This makes code reusable, cleaner, and saferβespecially in large environments.
1. Terraform Variables β Practical Understanding
Why Variables Exist (Real Problem)
Hard-coding repeated values causes problems:
- VPN IP appears in many security group rules
- Ports appear repeatedly
- Change = manual edits everywhere
- High chance of mistakes in production
β Variables solve this by centralizing values.
Identifying What Should Be a Variable
Rule of thumb:
Any value repeated more than once β make it a variable
Examples:
- VPN IP
- Ports (22, 21, 8080)
- AMI IDs
- Instance types
- Region-specific values
2. Creating Variables (Practical Example)
Step 1: Main Terraform code (resources)
Example:
cidr_ipv4 = var.vpn_ip
from_port = var.app_port
to_port = var.app_port
No hardcoded values.
Step 2: Define variables (recommended: variables.tf)
variable "vpn_ip" {
description = "VPN server IP address"
type = string
}
variable "app_port" {
description = "Application port"
type = number
}
β
No values yet
β
Just definitions
Step 3: Provide values centrally
You now decide where the values live.
Thatβs where .tfvars comes in.
3. .tfvars β Variable Definition File (VERY IMPORTANT)
Purpose of .tfvars
- Holds values, not variable definitions
- Keeps environment-specific configuration separate
- Makes code reusable and safe
Standard Files (Best Practice)
main.tf
variables.tf
terraform.tfvars
Example terraform.tfvars:
vpn_ip = "10.10.10.10/32"
app_port = 8080
ssh_port = 22
ftp_port = 21
β
Terraform automatically loads terraform.tfvars
Multiple Environments (Real Production Use)
dev.tfvars
stage.tfvars
prod.tfvars
Example:
# dev.tfvars
instance_type = "t3.micro"
# prod.tfvars
instance_type = "m5.large"
Use with:
terraform plan -var-file=prod.tfvars
4. Defaults vs .tfvars β Who Wins?
Variable with default:
variable "instance_type" {
default = "t2.micro"
}
If value exists in .tfvars:
β
.tfvars overrides default
Terraform precedence rule:
Explicit values always override defaults
5. Variable Naming Best Practices
β
Use variables.tf for variable definitions
β
Use .tfvars for values
β
Add descriptions
β
Avoid touching core resource files in production
Example:
variable "vpn_ip" {
description = "Corporate VPN IP allowed in security groups"
}
6. What Happens If No Value Is Provided?
If:
- Variable defined
- No default
- No
.tfvars - No CLI / env value
π Terraform prompts for input:
var.instance_type
Enter a value:
This works, but not ideal for automation.
7. Ways to Assign Variable Values (IMPORTANT)
Terraform supports 4 main methods.
1οΈβ£ Default Value
variable "instance_type" {
default = "t2.micro"
}
β Lowest priority
2οΈβ£ .tfvars File (Most Common)
instance_type = "m5.large"
β Recommended for environments
3οΈβ£ Command Line -var
terraform plan -var="instance_type=m5.large"
β
Overrides defaults
β
Useful for quick tests
4οΈβ£ Environment Variables (CI/CD Friendly)
Naming rule (case-insensitive):
TF_VAR_<variable_name>
Example:
TF_VAR_instance_type=m5.large
β
Great for pipelines
β
No files needed
8. Environment Variables β Windows
Create variable
TF_VAR_instance_type = t2.large
β οΈ Important:
- You must restart terminal
- Existing terminals wonβt see new variables
Verify:
echo %TF_VAR_instance_type%
9. Environment Variables β Linux / Mac
Set variable
export TF_VAR_instance_type=m5.large
Verify:
echo $TF_VAR_instance_type
Terraform:
terraform plan
β Terraform picks it automatically
10. Variable Assignment Precedence (MEMORIZE)
From highest β lowest priority:
- CLI
-var - Environment variables
TF_VAR_* -
.tfvarsfile - Default value
- Prompt (if nothing provided)
11. Production Guidelines (Very Important)
β
Never hardcode repeated values
β
Keep resource files unchanged across environments
β
Store values in .tfvars
β
Use env vars in CI/CD
β
Always add variable descriptions
This minimizes:
- Human errors
- Risky edits
- Broken infrastructure
12. Interview-Ready Summary
Q: How can you assign values to Terraform variables?
Answer:
Terraform variables can be assigned using default values,
.tfvarsfiles, command-line-varflags, environment variables prefixed withTF_VAR_, or interactive prompts if no value is provided.
β PART 1 β VARIABLE DEFINITION PRECEDENCE (MOST IMPORTANT)
Terraform allows setting variable values in MANY places:
- Default
- terraform.tfvars
- environment variables
- auto.tfvars
- CLI -var
- Prompts
If values DIFFER, which one wins?
β FINAL PRECEDENCE ORDER (from lowest β highest)
| Priority | Source | Example |
|---|---|---|
| 1οΈβ£ Lowest | Default | default = "t2.micro" |
| 2οΈβ£ | Environment variables | TF_VAR_instance_type=t2.small |
| 3οΈβ£ | terraform.tfvars | instance_type="t2.large" |
| 4οΈβ£ | terraform.tfvars.json | Same as above but JSON |
| 5οΈβ£ | auto.tfvars | dev.auto.tfvars |
| 6οΈβ£ Highest | CLI -var or -var-file | -var "instance_type=m5.large" |
β The LAST one wins
Terraform processes sources in order.
If multiple locations provide values β last one overrides earlier values.
π₯ EXAMPLES TO MAKE IT EASY
Example 1:
default = "t2.micro"
TF_VAR_instance_type = "t2.small"
terraform.tfvars β "t2.large"
Result: Terraform uses "t2.large" β tfvars overrides env + default.
Example 2:
default = "t2.micro"
terraform.tfvars β "t2.large"
CLI β -var="instance_type=m5.large"
Result: "m5.large" (CLI always wins)
π§ PRACTICAL DEMO LOGIC (how lecturer showed it)
- Default =
"t2.micro" - Add environment variable:
TF_VAR_instance_type = "t2.small"
Terraform plan β takes t2.small
- Add terraform.tfvars:
instance_type = "t2.large"
Terraform plan β takes t2.large
- Use CLI:
terraform plan -var "instance_type=m5.large"
Terraform plan β takes m5.large
β Perfect understanding.
β PART 2 β LIST DATA TYPE (VERY COMMON WITH AWS)
β What a LIST is:
- A sequence of values
- Written inside square brackets
- Comma-separated
Example:
["Mumbai", "Bangalore", "Delhi"]
Terraform understands this as a list of strings.
Why list is needed?
Because some AWS arguments expect multiple values.
Example:
EC2 β vpc_security_group_ids
Documentation says:
(Required) list of security group IDs
Meaning Terraform MUST receive:
vpc_security_group_ids = [
"sg-001",
"sg-002"
]
If you write incorrect format:
vpc_security_group_ids = "sg-001"
Terraform throws:
Incorrect attribute value type
β List can contain only ONE value too such as:
vpc_security_group_ids = ["sg-001"]
You STILL must keep list brackets because argument type is list, even if list length = 1.
β PART 3 β LIST WITH TYPE CONSTRAINTS
You can restrict what type goes inside a list.
Example:
variable "ports" {
type = list(number)
}
β Accepts: [22, 8080]
β Rejects: ["ssh", "http"]
If user enters incorrect type β Terraform errors:
Invalid value for input variable
Number required
Very useful for validation.
β PART 4 β Terraform BASIC DATA TYPES
Terraform supports:
1. string
"hello"
2. number
45
10.5
3. bool
true
false
4. list
Sequence of values:
["a", "b", "c"]
5. set
Unique unordered values:
set(string)
6. map
Key-value pairs:
{
Name = "web"
Team = "devops"
}
7. object
Structured schema:
object({
name = string
memory = number
})
8. tuple
List with fixed types:
tuple([string, number, bool])
β Practical AWS Examples for Each Data Type
String
instance_type = "t3.micro"
Number
volume_size = 20
List
availability_zones = ["us-east-1a", "us-east-1b"]
Map
tags = {
Name = "web"
Env = "dev"
}
β FINAL INTERVIEW SUMMARY
If the interviewer asks:
βExplain Terraform variable precedence.β
Your answer:
Terraform loads variables from several sources. If the same variable is defined in multiple places, Terraform uses the value from the source with the highest precedence. The order from lowest to highest is: default values, environment variables, terraform.tfvars, terraform.tfvars.json, auto.tfvars files, and finally CLI options using
-varor-var-file. CLI always wins.
βWhat is a list in Terraform?β
A list is a Terraform data type used to store multiple values in order, inside square brackets. Some AWS arguments, like
vpc_security_group_ids, require a list even when you have only one value. Example:["sg-123"]. Lists can also be type-restricted, likelist(string)orlist(number).
βWhat is the difference between data types like list, map, number, string?β
List is an ordered collection, map is key-value pairs, string is text, number is numeric value. Terraform determines the required type for each argument from documentation.
β PART 1 β MAP DATA TYPE (KEYβVALUE PAIRS)
1. What is a Map in Terraform?
A map is a collection of keyβvalue pairs.
variable "instance_tags" {
type = map(string)
default = {
Name = "app-server"
Environment = "dev"
Team = "payments"
}
}
- Each key is unique
- Each key has exactly one value
- Values are accessed by key name
2. When to Use Map (Very Important)
You use map when:
- Data is in key β value format
- Keys have meaning (not just position)
π₯ MOST COMMON USE CASE β AWS TAGS
AWS Tags are not lists.
They are maps.
AWS Console example:
Key: Name β Value: web
Key: Environment β Value: dev
Key: Team β Value: payments
Terraform:
tags = {
Name = "web"
Env = "dev"
}
π Thatβs why tags expects:
Map of tags to assign to the resource
3. Practical Map Demo Behavior
β Wrong (list instead of map)
["team=payments"]
Terraform error β
β Correct
{
team = "payments"
location = "us"
}
4. Default Map Values
variable "my_map" {
type = map(string)
default = {
name = "Alice"
team = "payments"
}
}
Terraform will not prompt for values.
β PART 2 β REFERENCING VALUES FROM MAPS & LISTS
This is VERY IMPORTANT for exams and real production code.
1. Referencing a Map Value
Map definition:
variable "types" {
type = map(string)
default = {
us-west-2 = "t2.nano"
ap-south-1 = "t2.small"
}
}
Usage:
instance_type = var.types["us-west-2"]
β
Output:
instance_type = t2.nano
Change key β value changes automatically.
2. Referencing a List Value
List definition:
variable "sizes" {
type = list(string)
default = ["m5.large", "m5.xlarge", "m5.2xlarge"]
}
Indexing rules:
| Index | Value |
|---|---|
| 0 | m5.large |
| 1 | m5.xlarge |
| 2 | m5.2xlarge |
Usage:
instance_type = var.sizes[1]
β
Output:
m5.xlarge
π Lists use positions, maps use keys
β PART 3 β COUNT META-ARGUMENT
1. What is count?
By default:
resource "aws_instance" "example" { }
π Creates ONE resource
With count:
count = 3
π Creates THREE identical resources
2. Why count Exists?
Without count:
- You must copy the same resource block many times
- Code becomes huge and unmaintainable
With count:
- One block
- Clean code
- Dynamic scaling
3. Count Resource Addressing
If:
resource "aws_instance" "my_ec2" {
count = 3
}
Terraform creates:
aws_instance.my_ec2[0]
aws_instance.my_ec2[1]
aws_instance.my_ec2[2]
Indexes always start from 0.
4. Major Problem with count (Critical)
count creates IDENTICAL COPIES.
Example problem:
resource "aws_iam_user" "user" {
name = "payments-user"
count = 3
}
AWS requires unique usernames.
Result:
- First user β created
- Second β fails
- Third β fails
β PART 4 β COUNT.INDEX (SOLUTION TO REAL PROBLEMS)
1. What is count.index?
count.index:
- Starts from 0
- Increments per resource
- Unique per instance
2. Using count.index for EC2 Names
Problem:
All EC2s have same Name tag
Solution:
tags = {
Name = "payments-system-${count.index}"
}
Results:
payments-system-0
payments-system-1
payments-system-2
β
Human-friendly
β
Debug-friendly
3. Fixing IAM User Uniqueness
resource "aws_iam_user" "user" {
count = 3
name = "payments-user-${count.index}"
}
Creates:
payments-user-0
payments-user-1
payments-user-2
β Works perfectly
4. Advanced Pattern β List + Count Index (REAL WORLD)
Variable:
variable "users" {
type = list(string)
default = ["Alice", "Bob", "John"]
}
Resource:
resource "aws_iam_user" "users" {
count = length(var.users)
name = var.users[count.index]
}
Creates:
Alice
Bob
John
π Very common in:
- IAM users
- EC2 hostnames
- Batch resources
5. Important Behavior to Remember
-
countdecides how many -
count.indexdecides which one - If list has more items than count β extra ignored
- If count > list length β Terraform errors
β FINAL EXAM-READY SUMMARY
Map vs List
| Type | Used for |
|---|---|
| map | Keyβvalue pairs (tags, configs) |
| list | Ordered values (SGs, AZs) |
Referencing
var.map["key"]
var.list[index]
count
- Creates multiple identical resources
- Index starts at 0
count.index
- Makes each instance unique
- Used for naming & customization
β PART 1 β CONDITIONAL EXPRESSIONS IN TERRAFORM
1. What is a Conditional Expression?
A conditional expression lets Terraform choose between two values based on a condition.
General Syntax
condition ? true_value : false_value
- If
conditionis true βtrue_valueis used - If
conditionis false βfalse_valueis used
2. Real-World Example: Choosing Instance Type by Environment
Variable:
variable "environment" {
default = "development"
}
Conditional Expression inside EC2:
instance_type = var.environment == "development" ?
"t2.micro" :
"m5.large"
Result:
| environment | instance_type |
|---|---|
| development | t2.micro |
| production / anything else | m5.large |
3. Testing the Behavior
Case 1 β Default βdevelopmentβ
terraform plan
β instance_type = t2.micro
Case 2 β Change to "production"
terraform plan
β instance_type = m5.large
4. Using "not equal" (!=)
instance_type = var.environment != "development" ?
"t2.micro" :
"m5.large"
Meaning:
- If environment is NOT development β t2.micro
- Otherwise β m5.large
5. Condition on Missing / Empty Values
If default is empty:
variable "environment" {}
Then:
instance_type = var.environment == "" ?
"t2.micro" :
"m5.large"
6. Using Multiple Conditions (Logical AND)
You can combine conditions:
instance_type = (var.environment == "production" &&
var.region == "us-east-1") ?
"m5.large" :
"t2.micro"
True only if:
- environment = production AND
- region = us-east-1
Otherwise β t2.micro
π§ EXAM TIP
Conditional expressions are used heavily in module customization:
- region-specific defaults
- environment-based sizing
- enabling or disabling resources
Keep the syntax memorized:
condition ? true : false
β PART 2 β TERRAFORM FUNCTIONS
1. What is a Function?
A function in Terraform:
- accepts input
- returns output
- performs a specific task
This is the same concept you see in Python.
2. Example β max() Function
max(10, 30, 20) β 30
Terraform decides the largest number.
3. Example β file() Function
file("random-file.txt")
Returns the entire content of the file as a string.
This is extremely useful for:
- IAM policies
- JSON configurations
- certificates
- shell scripts
β PART 3 β TERRAFORM CONSOLE (Testing Functions)
Run:
terraform console
Then test any function:
max()
> max(10, 50, 20)
50
file()
> file("random-file.txt")
"This is a test file"
Terraform console is powerful:
- test expressions
- test variables
- test functions
- avoid trial-and-error inside real plan/apply
β PART 4 β WHY FUNCTIONS MATTER (REAL WORLD)
1. The Problem (without functions)
A policy embedded directly inside .tf file:
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
...
]
}
EOF
Issues:
- makes Terraform file huge
- editing JSON inside HCL is error-prone
- no code reuse
- poor readability
2. The Solution β Use file() Function
Move JSON to a separate file:
iam-user-policy.json
Then reference:
policy = file("iam-user-policy.json")
Benefits:
- cleaner Terraform code
- easier editing
- reusable
- reduces risk of JSON formatting errors
- separates logic from content
This is used everywhere:
- IAM JSON policies
- security configs
- Lambda zip handling
- template rendering
π§ EXAM TIP
You cannot create custom functions.
Terraform only supports built-in functions.
Quote:
Terraform language does not support user-defined functions.
Important categories to remember:
- numeric functions (max, min, ceil, floor)
- string functions (upper, lower, trimspace)
- collection functions (length, keys, values, contains, lookup)
- filesystem functions (file, templatefile)
- encoding functions (jsonencode, base64encode)
π§ MOST IMPORTANT FUNCTIONS FOR EXAM & REAL JOB
jsonencode()
Convert HCL map β JSON string
Used for IAM policies and Kubernetes manifests.
file()
Load external file.
templatefile()
Load file + replace variables.
lookup()
Safe read from maps.
length()
Length of list or map.
keys() / values()
Extract keys/values.
element()
Get list element with wrap-around.
coalesce()
Return first non-null value.
π― FINAL SUMMARY (Quick Revision)
Conditional Expressions
condition ? true_value : false_value
Used for:
- choosing instance types
- enabling/disabling resources
- environment-based logic
Terraform Functions
- Built-in helpers for logic, math, strings, file loading
- Cannot define your own
- Massive time-saver and code reducer
file() Function
Loads external files β essential for:
- policies
- JSON configs
- templates
β 1. Terraform Functions β What They Are & How to Use Them
Definition
A function in Terraform:
- takes some input
- performs a specific calculation or operation
- returns output
Examples:
max(10, 20, 30) # β 30
file("demo.txt") # β returns content of file
length(["a","b","c"]) # β 3
Where to use functions
Functions appear inside:
- resource arguments
- variables
- locals
- outputs
Example:
ami = lookup(var.ami, var.region)
Key Functions in the Challenge Code
1. lookup()
Purpose: Get a value from a map using a key.
lookup(var.ami, var.region)
If:
ami = {
"us-east-1" = "ami-123"
"us-west-2" = "ami-456"
}
region = "us-east-1"
Then:
lookup(var.ami, var.region) β "ami-123"
2. length()
Returns the number of items in a list, string, or map.
length(var.tags) # If tags = ["a", "b"], result = 2
Often used with count:
count = length(var.tags)
3. element()
Returns item at index from a list.
element(var.tags, count.index)
If:
tags = ["first EC2", "second EC2"]
Then during creation:
- first EC2 instance β
first EC2 - second EC2 instance β
second EC2
4. timestamp()
Returns the current time in RFC3339 format.
Example:
2024-06-17T10:52:01Z
5. formatdate()
Converts timestamp to readable format:
formatdate("DD/MM/YYYY", timestamp())
Output example:
17/06/2024
β 2. Local Values (locals)
What are locals?
Locals let you store reusable expressions, similar to variables, but:
- cannot be overridden (unlike variables)
- often store computed values
- allow functions inside
Example:
locals {
common_tags = {
Team = "security-team"
CreationDate = formatdate("YYYY-MM-DD", timestamp())
}
}
Use in resources:
tags = local.common_tags
Why locals instead of variables?
Use variables when:
- users or pipelines need to overwrite values
- value is an input
Use locals when:
- you want to compute a value
- the value should NOT be overridden
- repeated expressions must be centralized
π₯ Important Rule
Definition uses locals (plural), but reference uses local (singular).
Example:
locals { common_tags = {...} }
tags = local.common_tags
β 3. Data Sources β The Most Important Concept
Definition
Data sources let Terraform read information that already exists outside Terraform.
Terraform does NOT create anything with data sources.
Uses:
- fetch existing EC2 IDs
- get AMIs
- get VPC IDs
- read files
- fetch Azure/DigitalOcean metadata
Basic Format
data "<provider>_<type>" "<name>" {
# optional filters
}
Example:
data "aws_instances" "all" {}
Data is stored in terraform.tfstate.
π Examples Explained
1. DigitalOcean Account Data Source
data "digitalocean_account" "info" {}
Fetches:
- droplet limits
- floating IP limits
Stored in state, not printed unless you output it.
2. Read Local File
data "local_file" "demo" {
filename = "${path.module}/demo.txt"
}
Outputs:
data.local_file.demo.content
What is path.module?
It returns the directory where the current .tf file is located.
3. AWS EC2 Instances
Multiple instances
data "aws_instances" "all" {}
Retrieves:
- private_ips
- public_ips
- instance_ids
Single instance
data "aws_instance" "example" {
instance_id = "i-123..."
}
β οΈ If more than one instance matches, Terraform errors until you filter.
β 4. Filters in Data Sources
Filters are key to narrowing down resources.
Example: Find EC2 instance by tag
data "aws_instance" "prod" {
filter {
name = "tag:team"
values = ["production"]
}
}
Now Terraform will fetch only:
instance where tag team = production
If more instances match, Terraform still throws an error β this data source supports only one match.
For multiple EC2s β use:
data "aws_instances" "prod" {
filter {
name = "tag:team"
values = ["production"]
}
}
π FINAL SUMMARY (fast revision)
Functions
- lookup() β read from map
- length() β count items
- element() β pick from list
- timestamp() β current time
- formatdate() β readable date
Local Values
- reusable computed expressions
- allow functions
- cannot be overridden
Data Sources
- fetch information outside Terraform
- do NOT create resources
- stored in tfstate
- use filters for precision
Filters
Allow you to fetch:
- by name
- by AMI
- by tags
- by attributes
β 1. Understanding Terraform Functions (Lookup / Length / Element / Formatdate / Timestamp)
Terraform functions are used to calculate values dynamically instead of hardcoding them.
Example Code
Inside an EC2 resource we may see:
ami = lookup(var.ami, var.region)
count = length(var.tags)
tags = {
Name = element(var.tags, count.index)
CreationDate = formatdate("DD MMM YYYY", timestamp())
}
Now break each function down.
πΉ lookup(map, key, default)
Purpose: Get a value from a map.
Example:
variable "ami" {
type = map(string)
default = {
"us-east-1" = "ami-1111"
"us-west-2" = "ami-2222"
}
}
lookup(var.ami, "us-east-1") β "ami-1111"
If key doesnβt exist β return default.
πΉ length(list/map/string)
Returns number of items.
length(["a","b"]) β 2
length("hello") β 5
Used for count = length(var.tags).
πΉ element(list, index)
Retrieve item from list by index.
element(["a", "b", "c"], 1) β "b"
When combined with count.index, it labels resources properly:
first EC2
second EC2
πΉ timestamp()
Returns UTC timestamp (not human friendly).
Example:
2024-07-11T14:17:30Z
πΉ formatdate()
Converts timestamp into readable format.
formatdate("DD MM YYYY", timestamp())
This becomes something like:
11 07 2024
β 2. Local values (βlocalsβ)
Locals allow you to centralize repeated values.
Example
locals {
common_tags = {
Team = "security-team"
CreationDate = formatdate("YYYY-MM-DD", timestamp())
}
}
Then in resources:
tags = local.common_tags
Difference between variables and locals
| Feature | Variables | Locals |
|---|---|---|
| Can be overwritten? | YES (tfvars, CLI, env) | NO |
| Useful for inputs | YES | NO |
| Useful for computed expressions | OK | BEST |
Use locals when value must not change outside code.
β 3. Data sources
Data sources fetch external information that Terraform does not create.
Examples:
- fetch AMI IDs
- fetch existing VPCs
- read files
- get instance details
πΉ Types of data sources
1. DigitalOcean Account
data "digitalocean_account" "do" {}
This fetches details about your DO account.
2. Local File
data "local_file" "foo" {
filename = "${path.module}/demo.txt"
}
Reads content of a file.
3. AWS Instances
data "aws_instances" "all" {}
Returns IDs of all EC2s in region.
Data source output stored in:
terraform.tfstate
β 4. Using Data Source to Get Latest AMI (MOST IMPORTANT PRACTICAL USE CASE)
This is the most real-world task.
Goal: Never hardcode AMI IDs. They differ per region.
πΉ Wrong / static approach
ami = "ami-12345678"
Fails if region changes.
πΉ Correct approach using aws_ami data source
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
Use in EC2:
resource "aws_instance" "server" {
ami = data.aws_ami.ubuntu.image_id
instance_type = "t3.micro"
}
This works in any region.
β 5. Debugging Terraform
Debug logs useful when things fail.
Enable debug logs
Linux / Mac
export TF_LOG=TRACE
export TF_LOG_PATH=terraform.log
Windows
set TF_LOG=TRACE
set TF_LOG_PATH=terraform.log
Now run:
terraform plan
Logs will appear in terraform.log.
β 6. Troubleshooting Model
4 types of Terraform errors:
- Language errors
- syntax errors
- undeclared variables
- unsupported arguments
- State errors
- lock issues
- drift
- corruption
- Core Terraform bugs
- in Terraform binary
- check GitHub issues
- Provider plugin bugs
- AWS provider bugs
- must update provider
β 7. Terraform fmt (formatting)
Fixes messy code indentation.
terraform fmt
β 8. Load Order & File Organization
Terraform loads all .tf files in the folder.
Best practice:
provider.tf
variables.tf
outputs.tf
ec2.tf
iam_user.tf
vpc.tf
β 9. Dynamic Blocks
Used when a nested block repeats many times.
Without dynamic block (bad):
ingress {
from_port = 8200
to_port = 8200
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 8201
to_port = 8201
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
With dynamic block (best):
variable "sg_ports" {
type = list(number)
default = [8200, 8201, 8300, 9500]
}
resource "aws_security_group" "dynamic_sg" {
dynamic "ingress" {
for_each = var.sg_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
You can also use:
iterator = port
from_port = port.value
β 10. terraform validate
Check syntax without deploying:
terraform validate
It catches:
- undeclared variables
- unsupported arguments
- syntax issues
β 11. terraform taint / replace
Old command:
terraform taint aws_instance.web
New recommended command:
terraform apply -replace="aws_instance.web"
Purpose:
- force recreate a resource (when manually changed, corrupted, or broken).
Below is a clean, structured, beginner-friendly rewrite of everything you shared, with clear explanations, correct terminology, and practical examples, exactly aligned with Terraform best practices.
I kept it teachable, interview-ready, and production-oriented.
β 1. Terraform Splat Expression
What is a Splat Expression?
A splat expression is a shortcut that lets you collect the same attribute from multiple resources into a list.
This is most commonly used when:
- a resource is created using
count - you want output for all instances, not just one
Example: IAM Users with count
resource "aws_iam_user" "lb" {
count = 3
name = "user-${count.index}"
}
Terraform creates:
- aws_iam_user.lb[0]
- aws_iam_user.lb[1]
- aws_iam_user.lb[2]
Without splat (single resource)
output "first_user_arn" {
value = aws_iam_user.lb[0].arn
}
Outputs ARN of only one user.
With splat (all resources)
output "all_user_arns" {
value = aws_iam_user.lb[*].arn
}
β Returns list of all ARNs, no matter how many users exist.
β Why splat is powerful
- Works for 3 resources or 300
- No manual indexing
- Perfect for outputs and cross-module usage
β 2. Terraform Graph
What is terraform graph?
It generates a dependency graph showing how resources depend on each other.
Terraform uses this dependency tree internally to:
- decide creation order
- decide destruction order
- parallelize safely
Example Dependencies
Provider (AWS)
β
βββ EC2 instances
β
βββ Load Balancer β depends on EC2
β
βββ Route53 β depends on Load Balancer
Generate Graph Output
terraform graph
This outputs DOT language, which looks unreadable.
Visualize Graph (Online)
- Search: graphviz online
- Paste DOT output
- See dependency diagram
β οΈ Not recommended for sensitive infra
Visualize Graph (Offline β Recommended)
Install Graphviz
Ubuntu / Debian
sudo apt install graphviz
Mac
brew install graphviz
Generate image
terraform graph | dot -Tpng graph.png
terraform graph | dot -Tsvg graph.svg
Open image locally β β safe & secure.
Why Terraform Graph matters
- Understand complex dependencies
- Debug destroy/apply issues
- Review infra architecture visually
β 3. Saving Terraform Plan to a File
Standard Workflow (Risky in Production)
terraform plan
terraform apply
Problem:
- Code may change between plan & apply
- Results may differ
β Recommended Production Workflow
terraform plan -out=infra.plan
terraform apply infra.plan
β Apply uses exact plan, regardless of code changes.
Example: Why it matters
- Create plan:
terraform plan -out=infra.plan
- Modify resource afterward
- Apply saved plan:
terraform apply infra.plan
β
Terraform ignores new changes
β
Infrastructure matches approved plan
Inspect Saved Plan
Human-readable
terraform show infra.plan
JSON (automation / auditing)
terraform show -json infra.plan | jq
β Required for:
- CI/CD pipelines
- Change management
- Security reviews
β
4. terraform output Command
Purpose
Extract output values from state file without running apply.
Example Output Block
output "iam_names" {
value = aws_iam_user.lb[*].name
}
Retrieve Outputs Later
terraform output
terraform output iam_names
terraform output iam_arn
β
No apply
β
No re-creation
β
Reads state safely
β 5. Terraform Settings Block
The terraform block controls project behavior, not resources.
Example Terraform Block
terraform {
required_version = "= 1.9.1"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.54.1"
}
}
}
Why this matters
Without settings:
- Terraform uses latest provider (may break)
- Version mismatch risk
With settings:
- Predictable builds
- Safe upgrades
- Production stability
Lock file update
If provider version changes:
terraform init -upgrade
β
6. Resource Targeting (-target)
Default Terraform Behavior
Applies all resources in folder.
Target single resource
terraform plan -target=local_file.foo
terraform apply -target=local_file.foo
terraform destroy -target=local_file.foo
β
Only that resource is affected
β
Others untouched
When to use -target
β
Emergency fixes
β
Debugging state issues
β
Partial infra recovery
π« Not for daily workflows
β 7. Terraform at Scale β API Throttling Problem
Real Problem in Large Infra
- Hundreds of resources
- Every
terraform plancalls APIs - Cloud provider rate limits exceeded
- Production impact
Example Scenario
- CIS hardening rules
- Hundreds of AWS API calls
- Terraform refresh = excessive load
β 8. Solutions to Terraform Scale Issues
β Solution 1: Split Projects
Instead of one giant repo:
vpc/
iam/
security-groups/
ec2/
β
Less API calls
β
Faster plans
β
Independent deployment
β Solution 2: Resource Targeting
Apply resources one-by-one:
terraform apply -target=aws_security_group.web
β Controlled API usage
β Solution 3: Disable Refresh (Advanced)
terraform plan -refresh=false
β
Skips state refresh
β
Reduces API calls
β οΈ Use only if state is trusted
When to avoid -refresh=false
- State drift likely
- Manual console changes
- Non-production environments
β FINAL SUMMARY
You now understand:
β
Splat expressions
β
Terraform graph visualization
β
Saving & applying plans safely
β
terraform output usage
β
Terraform project settings
β
Resource targeting
β
Handling large infra & API throttling
Terraform zipmap() Function
What is zipmap()?
zipmap() is a Terraform function that creates a map by pairing two lists:
- one list of keys
- one list of values
Each key is matched with the value at the same index.
Basic Syntax
zipmap(keys, values)
-
keysβ list of strings -
valuesβ list of values - Both lists must have the same length
Simple Example
zipmap(
["pineapple", "orange", "strawberry"],
["yellow", "orange", "red"]
)
Output:
{
pineapple = "yellow"
orange = "orange"
strawberry = "red"
}
Terraform pairs elements by index:
- pineapple β yellow
- orange β orange
- strawberry β red
Trying in Terraform Console
terraform console
zipmap(["a", "b"], ["1", "2"])
Output:
{
"a" = "1"
"b" = "2"
}
Practical Use Case: IAM Users
Problem
You create multiple IAM users using count and get:
- one output for names
- one output for ARNs
This becomes hard to read and correlate.
IAM Resource
resource "aws_iam_user" "iamuser" {
count = 3
name = "iamuser.${count.index}"
}
Separate Outputs (Hard to Read)
output "iam_names" {
value = aws_iam_user.iamuser[*].name
}
output "iam_arns" {
value = aws_iam_user.iamuser[*].arn
}
Result:
- Names list
- ARNs list β No clear mapping
β
Better Output Using zipmap
output "iam_name_to_arn" {
value = zipmap(
aws_iam_user.iamuser[*].name,
aws_iam_user.iamuser[*].arn
)
}
Output:
{
iamuser.0 = "arn:aws:iam::123456789:user/iamuser.0"
iamuser.1 = "arn:aws:iam::123456789:user/iamuser.1"
iamuser.2 = "arn:aws:iam::123456789:user/iamuser.2"
}
β
Easy to read
β
Easy to consume in modules
β
Very useful in large projects
Comments in Terraform
Terraform supports three comment styles.
1. Hash (#) β Recommended
# This creates an EC2 instance
resource "aws_instance" "example" {
instance_type = "t2.micro"
}
β
Most commonly used
β
Clean and readable
2. Double Slash (//)
// This is also a single-line comment
β
Works like #
β Less commonly used
3. Multi-line Comments (/* */)
/*
This is a multi-line comment.
Used for explanations or temporarily disabling code.
*/
Commenting Out a Resource Temporarily
/*
resource "aws_instance" "temp" {
ami = "ami-123"
instance_type = "t2.micro"
}
*/
β
Useful during testing
β
Resource will NOT be created
β οΈ Remember to close */ or Terraform will comment everything below it.
Terraform Meta-Arguments β Introduction
What is a Meta-Argument?
Meta-arguments change how Terraform behaves, not what it creates.
They are added inside resource blocks.
Default Terraform Resource Behavior
Terraform:
- Creates resources present in config but missing in state
- Destroys resources in state but missing from config
- Updates resources in-place if possible
- Destroys & recreates resources if in-place update is not possible
Examples
In-place Update
Changing a tag:
tags = {
Name = "HelloWorld"
}
β Change to "HelloEarth"
β
No destroy, just update
Force Replacement
Changing AMI:
ami = "linux-ami"
β change to Windows AMI
β EC2 instance is destroyed and recreated
Problem in Real Life
Someone manually updates a resource in AWS Console.
Example:
- Adds a tag:
Env=production
Terraform behavior:
- Detects drift
- Removes the tag during next
terraform apply
β Sometimes this is not what you want.
Lifecycle Meta-Argument
Purpose
Allows you to customize Terraformβs default behavior.
Example: Ignore Manual Tag Changes
resource "aws_instance" "example" {
ami = "ami-123"
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
lifecycle {
ignore_changes = [tags]
}
}
What This Does
- Terraform will ignore any manual tag changes
- Tags added in AWS Console remain untouched
- Terraform will not try to revert them
β Very common in production
Remove lifecycle Block
Terraform goes back to default behavior:
- Detects drift
- Fixes it
Lifecycle Meta-Argument β All Options (Overview)
Inside lifecycle {} you can use:
1. ignore_changes
Ignore specific attribute changes.
β Most commonly used
2. create_before_destroy
Create new resource first, then destroy old one.
β Prevents downtime
3. prevent_destroy
Blocks accidental deletion.
lifecycle {
prevent_destroy = true
}
β Very useful for:
- Databases
- Production storage
- Critical infrastructure
4. replace_triggered_by
Forces replacement when a referenced resource changes.
β Useful for hard dependencies
Other Important Meta-Arguments (Overview)
Terraform supports:
countfor_eachdepends_onproviderlifecycle
β οΈ provider meta-argument is not the same as provider block
It overrides which provider is used per resource.
Final Summary
You now understand:
β
zipmap() and when to use it
β
How to write clean Terraform comments
β
Default Terraform resource behavior
β
What meta-arguments are
β
Why lifecycle meta-argument is critical
β
All lifecycle options at a high level
Terraform Lifecycle + Meta-Arguments (Simple Explanations + Examples)
1. create_before_destroy
Purpose
Make Terraform create the new resource first, and destroy the old one later.
Why needed?
In production, you do not want downtime.
Default Terraform behavior is:
- Destroy old
- Create new
This causes downtime.
How to use
resource "aws_instance" "demo" {
ami = var.ami
instance_type = "t2.micro"
lifecycle {
create_before_destroy = true
}
}
Effect
Terraform will:
- Create new instance
- Terminate old instance
2. prevent_destroy
Purpose
Stop Terraform from destroying a resource at all.
Use case
-
Prevent accidental deletion of:
- Production databases
- KMS keys
- VPCs
- S3 buckets with important data
How to use
lifecycle {
prevent_destroy = true
}
Important
If you delete the entire resource block from tf code β Terraform WILL destroy it.
Prevent_destroy only works if the block is still in configuration.
3. ignore_changes
Purpose
Tell Terraform to ignore manual changes done outside Terraform.
Use case
- DevOps team changed EC2 instance type manually
- Someone added tags manually
- Auto-scaling updated some attributes
Examples
Ignore only Tags
lifecycle {
ignore_changes = [tags]
}
Ignore Tags + Instance Type
lifecycle {
ignore_changes = [tags, instance_type]
}
Ignore ALL changes
lifecycle {
ignore_changes = [all]
}
When using
[all], Terraform will never update the resource even if you modify the.tffile.
4. Resource Dependencies (depends_on)
There are two types:
A) Explicit dependency (depends_on)
Use when Terraform cannot automatically understand the relationship.
resource "aws_instance" "app" {
depends_on = [aws_s3_bucket.data]
ami = var.ami
instance_type = "t2.micro"
}
Effect
- Terraform creates S3 bucket first
- Then creates EC2 instance
Use case
- S3 bucket must exist before EC2 starts
- IAM role must exist before Lambda
- SG must exist before RDS
B) Implicit dependency
Terraform automatically understands dependency when a resource references another using attributes.
Example:
vpc_security_group_ids = [aws_security_group.prod.id]
Because you referenced .id, Terraform will:
- Create security group first
- Create EC2 instance second
This is implicit ordering.
Use case
Whenever one resource needs an output from another.
5. count vs for_each
A) count
When to use
- When all resources are identical
- No differences in configuration
Example
resource "aws_instance" "web" {
count = 3
ami = var.ami
instance_type = "t2.micro"
}
Problems with count
If you change the order of list β Terraform will recreate resources.
Bad for production.
B) for_each (better for production)
When to use
- When every item needs a unique config
- When order must not break resources
-
When input is:
- set
- map
Example 1: For each with Set
variable "users" {
type = set(string)
default = ["alice", "bob", "john"]
}
resource "aws_iam_user" "u" {
for_each = var.users
name = each.value
}
Example 2: For each with Map
variable "amis" {
type = map(string)
default = {
dev = "ami-111"
prod = "ami-222"
}
}
resource "aws_instance" "vm" {
for_each = var.amis
ami = each.value
tags = {
Name = each.key
}
}
6. List vs Set (Very Simple)
| Feature | List | Set |
|---|---|---|
| Order matters | Yes | No |
| Can have duplicates | Yes | No |
| Indexing | Yes (0,1,2) | No |
| Best used for | ordered data | unique items |
Example
List:
["a", "b", "b"]
Set:
["a", "b"]
Terraform will remove duplicate "b".
7. Object Data Type
Purpose
Advanced structure with different types for each attribute.
Example
variable "user" {
type = object({
name = string
age = number
country = string
})
}
Value example
user = {
name = "Alice"
age = 30
country = "USA"
}
Extra attributes are ignored
If user gives more fields β Terraform discards them.
8. Map Data Type
Purpose
Key/value data. All values must have same type.
Example
variable "details" {
type = map(number)
}
Valid:
{ age = 30, salary = 5000 }
Not valid:
{ age = 30, name = "bob" } # Wrong (string + number)
Top comments (0)