A. two ready-to-run Terraform examples (one count demo, one for_each demo),
B. step-by-step instructions to run and inspect results,
C. a clear explanation of meta-arguments and best practices, and (D) cleanup & cost warnings. Replace (AMI, region, key name, etc.) before running.
- Quick safety note: the AWS EC2 examples will create real instances that can incur cost. If you only want to experiment without charges, use the local_file or null_resource variants (I include a lightweight local demo below).
A — Example 1 — count (create N identical resources)
Purpose: create N identical resources (same config). Use count when you want a number of identical copies.
Create folder day69-count/ and files:
main.tf
terraform {
required_providers {
aws = { source = "hashicorp/aws" version = "~> 5.0" }
}
}
provider "aws" {
region = var.region
}
resource "aws_instance" "server" {
count = var.instance_count
ami = var.instance_ami
instance_type = var.instance_type
key_name = var.key_name
tags = {
Name = "server-${count.index}"
}
}
variables.tf
variable "region" {
type = string
default = "us-east-1"
}
variable "instance_count" {
type = number
default = 2
}
variable "instance_ami" {
type = string
default = "ami-08c40ec9ead489470" # replace for your region
}
variable "instance_type" {
type = string
default = "t2.micro"
}
variable "key_name" {
type = string
default = "<YOUR_KEY_PAIR>"
}
outputs.tf
output "instance_ids" {
value = aws_instance.server[*].id
}
output "instance_public_ips" {
value = aws_instance.server[*].public_ip
}
Run:
terraform init
terraform plan -out plan.tfplan
terraform apply "plan.tfplan"
Inspect addresses / outputs:
• Resources created are referenced as aws_instance.server[0], aws_instance.server[1], etc.
• count.index inside the block gives the zero-based index.
Change count:
• Update var.instance_count = 4 (or pass -var="instance_count=4"), run terraform apply — Terraform will create additional instances for indices 2 and 3.
B — Example 2 — for_each (create resources with distinct values)
Purpose: multiple similar resources which have different attributes (AMIs, names, or other per-item configuration). Use for_each with a map or set-of-strings so each instance is keyed and stable.
Create folder day69-foreach/ and files:
main.tf
terraform {
required_providers {
aws = { source = "hashicorp/aws" version = "~> 5.0" }
}
}
provider "aws" {
region = var.region
}
# map of named instances -> ami
variable "servers_map" {
type = map(string)
default = {
"linux" = "ami-0b0dcb5067f052a63"
"ubuntu" = "ami-08c40ec9ead489470"
}
}
resource "aws_instance" "server" {
for_each = var.servers_map
ami = each.value
instance_type = var.instance_type
key_name = var.key_name
tags = {
Name = "server-${each.key}"
}
}
variables.tf
variable "region" {
type = string
default = "us-east-1"
}
variable "instance_type" {
type = string
default = "t2.micro"
}
variable "key_name" {
type = string
default = ""
}
outputs.tf
output "server_public_ips" {
value = { for k, r in aws_instance.server : k => r.public_ip }
}
Run: same terraform init && terraform apply.
Inspect addresses / outputs:
• Resources are referenced by key: e.g. aws_instance.server["linux"], aws_instance.server["ubuntu"].
• each.key and each.value available inside the block.
Change for_each map:
• Add or remove keys in servers_map then terraform apply. Terraform will create or destroy only the specific keyed resources — stable mapping reduces accidental replacements.
C — Lightweight demo (no AWS charges) — use local_file and null_resource
If you want to learn semantics without creating cloud resources, try this local demo:
Folder day69-local/:
main.tf
resource "local_file" "count_files" {
count = 3
filename = "${path.module}/count_file_${count.index}.txt"
content = "file created with count index ${count.index}"
}
locals {
items = {
"one" = "first"
"two" = "second"
}
}
resource "local_file" "foreach_files" {
for_each = local.items
filename = "${path.module}/foreach_${each.key}.txt"
content = "for_each created ${each.key} => ${each.value}"
}
Run terraform init && terraform apply — you’ll get files in your folder; no cloud cost.
D — Meta-arguments: what they are & how to use them
Meta-arguments are special arguments accepted by resource/module blocks that control Terraform language behavior. Common meta-arguments include:
• count — number of instances (integer). Addresses use numeric indices: resource.type.name[index].
• for_each — iterate over a map or set; produces one instance per key. Addresses use keys: resource.type.name["key"].
• depends_on — explicit dependency list (useful in rare cases).
• provider — select which provider configuration to use.
• lifecycle — control create_before_destroy, prevent_destroy, ignore_changes.
• provisioner — (discouraged for most cases) run local/remote commands.
Count vs For_each — when to use which
• Use count:
• When you just need N identical copies.
• When order/indices are fine and attributes are identical.
• Example: count = 4 to create 4 identical web servers.
• Use for_each:
• When each instance needs unique attributes (different AMIs, names, sizes).
• When you want stable resource identity across changes (keys are stable).
• Use a map if you need key→value pairs (each.key and each.value).
• Use a set of strings if only names/IDs are needed.
Addressing differences
• count → aws_instance.foo[0], aws_instance.foo[1]
• for_each → aws_instance.foo["linux"], aws_instance.foo["ubuntu"]
State & lifecycle implications
• Changing count may cause Terraform to destroy the highest indexed resources if you reduce the number — which can be destructive.
• for_each keyed resources are more stable when adding/removing different keys.
• If you convert from count → for_each or vice versa, Terraform will typically want to recreate resources (state address changes). Use terraform state mv to migrate state safely if needed.
Tips & best practices
• Prefer for_each (map) for heterogeneous resources or when identity matters.
• Use toset() or tolist() conversions to ensure predictable input types.
• Avoid dynamic index assumptions; don’t depend on count.index to persist a machine’s identity over time.
• Keep resource blocks small and idempotent.
• Use lifecycle { create_before_destroy = true } when replacing resources that affect availability (but be careful).
• For large fleets, consider modules to group repeated logic and pass counts/for_each into module blocks.
E — Example: multiple key/value iteration (map with structured attributes)
If instances need multiple attributes:
variable "servers" {
default = {
web1 = { ami = "ami-aaa", instance_type = "t3.micro" }
web2 = { ami = "ami-bbb", instance_type = "t2.micro" }
}
}
resource "aws_instance" "web" {
for_each = var.servers
ami = each.value.ami
instance_type = each.value.instance_type
tags = { Name = each.key }
}
This is powerful — each.key becomes your stable identifier.
F — How to demonstrate for an interview (suggested steps)
1. Show the count example: instance_count = 2 apply, then change to 4 and apply — show terraform plan then apply.
2. Show the for_each example: add a new map key and terraform apply — point out only that key’s resource is created.
3. Show terraform state list and how addresses look (aws_instance.server[0] vs aws_instance.server["linux"]).
4. Explain migration: demonstrate terraform state mv if you rename keys or move from count→for_each.
5. Discuss real-world choice: use for_each for stable identity (e.g., DB replicas with different roles), count for identical worker nodes when identity is irrelevant (but note the stability issues).
G — Cleanup & cost control
• For AWS examples: terraform destroy -auto-approve to remove resources.
• Always confirm what will be destroyed with terraform plan -destroy.
• Use small instance types (e.g., t3.micro) and a single AZ for demos, or use local_file demo to avoid charges.
Top comments (0)