<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[StackOps - Diary]]></title><description><![CDATA[Welcome to the StackOps - Diary. We’re dedicated to empowering the tech community. We delve into cloud-native and microservices technologies, sharing knowledge ]]></description><link>https://stackopsdiary.site</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 02:33:38 GMT</lastBuildDate><atom:link href="https://stackopsdiary.site/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Kubernetes Network Policy]]></title><description><![CDATA[This is the sample network policy rules file for the demo lab. You can watch the full video on YouTube.
note.md
minikube start -p mininet --network-plugin=cni --cni=calico

kubectl create ns net-test
kubectl create ns net-test-1


kubectl run pod-a -...]]></description><link>https://stackopsdiary.site/kubernetes-network-policy</link><guid isPermaLink="true">https://stackopsdiary.site/kubernetes-network-policy</guid><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Sat, 06 Dec 2025 10:11:47 GMT</pubDate><content:encoded><![CDATA[<p>This is the sample network policy rules file for the demo lab. You can watch the full video on <a target="_blank" href="https://youtu.be/aP3hmDJ72h0">YouTube</a>.</p>
<p><code>note.md</code></p>
<pre><code class="lang-plaintext">minikube start -p mininet --network-plugin=cni --cni=calico

kubectl create ns net-test
kubectl create ns net-test-1


kubectl run pod-a -n net-test --image=busybox --restart=Never --command -- sleep 36000
kubectl run pod-c -n net-test --image=busybox --restart=Never --command -- sleep 36000
kubectl run pod-b -n net-test --image=nginx --restart=Never

kubectl expose pod pod-b -n net-test --port=80 --target-port=80 --type=ClusterIP
kubectl exec -n net-test pod-a -- wget -qO- http://pod-b.net-test.svc.cluster.local


kubectl run pod-a -n net-test-1 --image=busybox --restart=Never --command -- sleep 36000
kubectl run pod-b -n net-test-1 --image=busybox --restart=Never --command -- sleep 36000
kubectl exec -n net-test-1 pod-a -- wget -qO- http://pod-b.net-test.svc.cluster.local
</code></pre>
<p><code>deny-all.yaml</code></p>
<pre><code class="lang-plaintext">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: net-test
spec:
  podSelector:
    matchLabels:
      run: pod-b
  policyTypes:
  - Ingress
</code></pre>
<p><code>allow-from-pod-a.yaml</code></p>
<pre><code class="lang-plaintext">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-pod-a
  namespace: net-test
spec:
  podSelector:
    matchLabels:
      run: pod-b
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          run: pod-a
    ports:
    - protocol: TCP
      port: 80
</code></pre>
<p><code>allow-name-space.yaml</code></p>
<pre><code class="lang-plaintext">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-namespace
  namespace: net-test
spec:
  podSelector:
    matchLabels:
      run: pod-b
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: net-test-1
    ports:
    - protocol: TCP
      port: 80
</code></pre>
<p><code>allow-from-another-ns-pod.yaml</code></p>
<pre><code class="lang-plaintext">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-other-namespace
  namespace: net-test
spec:
  podSelector:
    matchLabels:
      run: pod-b
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: net-test-1   
          podSelector:
            matchLabels:
              run: pod-a                
      ports:
        - protocol: TCP
          port: 80
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Day 22: Terraform Import & Moving Resources]]></title><description><![CDATA[Welcome to Day 22! Today you’ll learn how to bring existing infrastructure under Terraform management and how to refactor your Terraform code without destroying resources.
🎯 Today’s Goals

Import existing resources into Terraform

Move resources wit...]]></description><link>https://stackopsdiary.site/day-22-terraform-import-and-moving-resources</link><guid isPermaLink="true">https://stackopsdiary.site/day-22-terraform-import-and-moving-resources</guid><category><![CDATA[technology]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Thu, 27 Nov 2025 10:23:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764238903459/89a3a267-c5b1-4cc1-90e6-d322d660cd1c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 22! Today you’ll learn how to bring existing infrastructure under Terraform management and how to refactor your Terraform code without destroying resources.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Import existing resources into Terraform</p>
</li>
<li><p>Move resources within the state.</p>
</li>
<li><p>Refactor configurations safely</p>
</li>
<li><p>Handle resource migrations</p>
</li>
</ul>
<h2 id="heading-terraform-import">📥 Terraform Import</h2>
<p><strong>Import</strong> adds existing infrastructure to Terraform state without recreating it.</p>
<h3 id="heading-basic-import-syntax"><strong>Basic Import Syntax</strong></h3>
<pre><code class="lang-bash">terraform import &lt;resource_address&gt; &lt;resource_id&gt;
</code></pre>
<h3 id="heading-import-process"><strong>Import Process</strong></h3>
<ol>
<li><p>Write resource block</p>
</li>
<li><p>Import into state</p>
</li>
<li><p>Run plan to verify</p>
</li>
<li><p>Adjust configuration if needed</p>
</li>
</ol>
<h3 id="heading-example-import-ec2-instance"><strong>Example: Import EC2 Instance</strong></h3>
<pre><code class="lang-plaintext"># Step 1: Write resource block
resource "aws_instance" "imported" {
  # Required arguments only initially
  ami           = "ami-12345"
  instance_type = "t2.micro"
}
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># Step 2: Import</span>
terraform import aws_instance.imported i-1234567890abcdef0
<span class="hljs-comment"># Step 3: Plan to see differences</span>
terraform plan
<span class="hljs-comment"># Step 4: Update configuration to match</span>
<span class="hljs-comment"># Add missing arguments from plan output</span>
</code></pre>
<h3 id="heading-common-resource-imports"><strong>Common Resource Imports</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># EC2 Instance</span>
terraform import aws_instance.web i-1234567890abcdef0

<span class="hljs-comment"># VPC</span>
terraform import aws_vpc.main vpc-12345678

<span class="hljs-comment"># S3 Bucket</span>
terraform import aws_s3_bucket.data my-bucket-name

<span class="hljs-comment"># Security Group</span>
terraform import aws_security_group.web sg-12345678

<span class="hljs-comment"># IAM Role</span>
terraform import aws_iam_role.app my-app-role

<span class="hljs-comment"># RDS Instance</span>
terraform import aws_db_instance.database my-database
</code></pre>
<h2 id="heading-moving-resources">🔄 Moving Resources</h2>
<h3 id="heading-within-same-state"><strong>Within Same State</strong></h3>
<p>Use <code>terraform state mv</code>:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Rename resource</span>
terraform state mv aws_instance.old aws_instance.new

<span class="hljs-comment"># Move to module</span>
terraform state mv aws_instance.web module.web.aws_instance.server

<span class="hljs-comment"># Move from module</span>
terraform state mv module.old.aws_instance.web aws_instance.web

<span class="hljs-comment"># Move with count/index</span>
terraform state mv <span class="hljs-string">'aws_instance.web[0]'</span> <span class="hljs-string">'aws_instance.primary'</span>
</code></pre>
<h3 id="heading-between-different-states"><strong>Between Different States</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Pull source statet</span>
erraform state pull &gt; source.tfstate

<span class="hljs-comment"># Pull destination state</span>
terraform state pull &gt; dest.tfstate
</code></pre>
<h2 id="heading-hands-on-lab">🧪 Hands-On Lab</h2>
<h3 id="heading-lab-1-import-existing-resources"><strong>Lab 1: Import Existing Resources</strong></h3>
<p><code>main.tf</code>:</p>
<pre><code class="lang-plaintext">terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# We'll import this later
resource "aws_s3_bucket" "imported" {
  bucket = "BUCKET_NAME"  # Replace after import

  tags = {
    Imported = "true"
  }
}
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># Create bucket manually first</span>
BUCKET_NAME=<span class="hljs-string">"import-lab-<span class="hljs-subst">$(date +%s)</span>"</span>
aws s3 mb s3://<span class="hljs-variable">$BUCKET_NAME</span>

<span class="hljs-comment"># Add bucket name to terraform</span>
sed -i <span class="hljs-string">"s/BUCKET_NAME/<span class="hljs-variable">$BUCKET_NAME</span>/"</span> main.tf

<span class="hljs-comment"># Import the bucket</span>
terraform import aws_s3_bucket.imported <span class="hljs-variable">$BUCKET_NAME</span>

<span class="hljs-comment"># Verify</span>
terraform plan  

<span class="hljs-comment"># Update tags</span>
terraform apply

<span class="hljs-comment"># Clean up</span>
terraform destroy
</code></pre>
<h3 id="heading-lab-2-move-resources"><strong>Lab 2: Move Resources</strong></h3>
<p><code>main.tf</code>:</p>
<pre><code class="lang-plaintext">resource "aws_instance" "old_name" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"

  tags = {
    Name = "test-instance"
  }
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># Apply</span>
terraform apply -auto-approve

<span class="hljs-comment"># Rename in code</span>
mv old_name to new_name <span class="hljs-keyword">in</span> main.tf

<span class="hljs-comment"># Move in state</span>
terraform state mv aws_instance.old_name aws_instance.new_name

<span class="hljs-comment"># Verify</span>
terraform plan  

<span class="hljs-comment"># Should show no changes</span>
<span class="hljs-comment"># Clean up</span>
terraform destroy
</code></pre>
<h2 id="heading-import-with-foreach">🔧 Import with For_Each</h2>
<pre><code class="lang-plaintext">resource "aws_s3_bucket" "imported" {
  for_each = toset([
    "bucket-1",
    "bucket-2",
    "bucket-3"
  ])

  bucket = each.value
}
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># Import each</span>
terraform import <span class="hljs-string">'aws_s3_bucket.imported["bucket-1"]'</span> bucket-1
terraform import <span class="hljs-string">'aws_s3_bucket.imported["bucket-2"]'</span> bucket-2
terraform import <span class="hljs-string">'aws_s3_bucket.imported["bucket-3"]'</span> bucket-3
</code></pre>
<h2 id="heading-best-practices">📋 Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Always back up the state before importing</strong></p>
<pre><code class="lang-bash"> terraform state pull &gt; backup.tfstate
</code></pre>
</li>
<li><p><strong>Import in stages</strong></p>
<ul>
<li><p>Import critical resources first</p>
</li>
<li><p>Test thoroughly</p>
</li>
<li><p>Document what was imported</p>
</li>
</ul>
</li>
<li><p><strong>Use</strong> <code>terraform plan</code> to verify</p>
<pre><code class="lang-bash"> terraform import &lt;resource&gt; &lt;id&gt;terraform plan  <span class="hljs-comment"># Should show minimal changes</span>
</code></pre>
</li>
<li><p><strong>Keep import scripts</strong></p>
<pre><code class="lang-bash"> <span class="hljs-comment"># import.sh</span>
 <span class="hljs-comment">#!/bin/bash</span>
 terraform import aws_vpc.main vpc-12345
 terraform import aws_subnet.public[0] subnet-12345
 terraform import aws_subnet.public[1] subnet-67890
</code></pre>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p><strong>Don’t import without backing up</strong></p>
</li>
<li><p><strong>Don’t modify state files manually</strong></p>
</li>
<li><p><strong>Don’t import into production without testing</strong></p>
</li>
<li><p><strong>Don’t forget to update configuration after import</strong></p>
</li>
</ol>
<h2 id="heading-troubleshooting">🔍 Troubleshooting</h2>
<h3 id="heading-import-fails"><strong>Import Fails</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Error: resource already in state</span>
terraform state rm aws_instance.web
terraform import aws_instance.web i-12345
<span class="hljs-comment"># Error: resource not found</span>
<span class="hljs-comment"># Check resource ID is correct</span>
aws ec2 describe-instances --instance-ids i-12345
</code></pre>
<h3 id="heading-plan-shows-changes-after-import"><strong>Plan Shows Changes After Import</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Get actual resource configuration</span>
terraform show
<span class="hljs-comment"># Update your .tf file to match</span>
</code></pre>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Importing existing resources</p>
</li>
<li><p>✅ Moving resources in state</p>
</li>
<li><p>✅ Refactoring without destruction</p>
</li>
<li><p>✅ Import blocks (Terraform 1.5+)</p>
</li>
<li><p>✅ Import best practices</p>
</li>
</ul>
<h2 id="heading-tomorrow-provisioners-amp-null-resources">🚀 Tomorrow: Provisioners &amp; Null Resources</h2>
<hr />
<p><a target="_blank" href="day22.md">← Day 21: Workspaces</a> | <a target="_blank" href="day24.md">Day 23: Provisioners →</a></p>
]]></content:encoded></item><item><title><![CDATA[Day 21: Workspaces for Environment Management]]></title><description><![CDATA[Welcome to Week 4! This week focuses on advanced Terraform techniques. Today we’ll master workspaces - a powerful feature for managing multiple environments with the same configuration.
🎯 Today’s Goals

Understand Terraform workspaces

Create and ma...]]></description><link>https://stackopsdiary.site/day-21-workspaces-for-environment-management</link><guid isPermaLink="true">https://stackopsdiary.site/day-21-workspaces-for-environment-management</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Tue, 04 Nov 2025 12:30:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762248201812/4d07d8c4-67c4-429e-90c4-149363bdc67b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Week 4! This week focuses on advanced Terraform techniques. Today we’ll master <strong>workspaces</strong> - a powerful feature for managing multiple environments with the same configuration.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Understand Terraform workspaces</p>
</li>
<li><p>Create and manage workspaces</p>
</li>
<li><p>Use workspaces for environment isolation</p>
</li>
<li><p>Learn workspace best practices and limitations</p>
</li>
</ul>
<h2 id="heading-what-are-workspaces">🌍 What Are Workspaces?</h2>
<p><strong>Workspaces</strong> allow you to manage multiple instances of a single configuration. Each workspace has its own state file.</p>
<pre><code class="lang-plaintext">Same Configuration → Multiple Workspaces → Separate States

my-infrastructure/
├── main.tf (shared)
├── terraform.tfstate.d/
│   ├── dev/terraform.tfstate
│   ├── staging/terraform.tfstate
│   └── prod/terraform.tfstate
</code></pre>
<h2 id="heading-workspace-commands">📚 Workspace Commands</h2>
<pre><code class="lang-bash"><span class="hljs-comment"># List workspaces</span>
terraform workspace list
<span class="hljs-comment"># Show current workspace</span>
terraform workspace show
<span class="hljs-comment"># Create new workspace</span>
terraform workspace new dev
<span class="hljs-comment"># Switch workspace</span>
terraform workspace select dev
<span class="hljs-comment"># Delete workspace (must be empty)</span>
terraform workspace delete dev
</code></pre>
<h2 id="heading-using-workspaces">🔧 Using Workspaces</h2>
<h3 id="heading-basic-usage"><strong>Basic Usage</strong></h3>
<pre><code class="lang-plaintext"># main.tf
resource "aws_instance" "app" {
  ami           = "ami-12345"
  instance_type = terraform.workspace == "prod" ? "t3.large" : "t2.micro"

  tags = {
    Name        = "app-${terraform.workspace}"
    Environment = terraform.workspace
  }
}
</code></pre>
<h3 id="heading-workspace-specific-values"><strong>Workspace-Specific Values</strong></h3>
<pre><code class="lang-plaintext">locals {
  env_config = {
    dev = {
      instance_type = "t2.micro"
      instance_count = 1
    }
    staging = {
      instance_type = "t2.small"
      instance_count = 2
    }
    prod = {
      instance_type = "t3.large"
      instance_count = 3
    }
  }

  config = local.env_config[terraform.workspace]
}

resource "aws_instance" "app" {
  count = local.config.instance_count

  ami           = data.aws_ami.amazon_linux.id
  instance_type = local.config.instance_type

  tags = {
    Name = "${terraform.workspace}-app-${count.index}"
  }
}
</code></pre>
<h2 id="heading-hands-on-lab">🧪 Hands-On Lab</h2>
<h3 id="heading-step-1-create-project"><strong>Step 1: Create Project</strong></h3>
<pre><code class="lang-bash">mkdir terraform-workspaces-lab
<span class="hljs-built_in">cd</span> terraform-workspaces-lab
</code></pre>
<p><code>main.tf</code>:</p>
<pre><code class="lang-plaintext">terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

locals {
  environments = {
    dev = {
      instance_type = "t2.micro"
      instance_count = 1
      cidr = "10.0.0.0/16"
    }
    staging = {
      instance_type = "t2.small"
      instance_count = 2
      cidr = "10.1.0.0/16"
    }
    prod = {
      instance_type = "t3.medium"
      instance_count = 3
      cidr = "10.2.0.0/16"
    }
  }

  env = local.environments[terraform.workspace]
}

resource "aws_vpc" "main" {
  cidr_block = local.env.cidr

  tags = {
    Name        = "${terraform.workspace}-vpc"
    Environment = terraform.workspace
  }
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = cidrsubnet(local.env.cidr, 8, 1)

  tags = {
    Name = "${terraform.workspace}-subnet"
  }
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

resource "aws_instance" "app" {
  count = local.env.instance_count

  ami           = data.aws_ami.amazon_linux.id
  instance_type = local.env.instance_type
  subnet_id     = aws_subnet.public.id

  tags = {
    Name        = "${terraform.workspace}-app-${count.index + 1}"
    Environment = terraform.workspace
  }
}

output "workspace" {
  value = terraform.workspace
}

output "vpc_id" {
  value = aws_vpc.main.id
}

output "instance_ids" {
  value = aws_instance.app[*].id
}
</code></pre>
<h3 id="heading-step-2-use-workspaces"><strong>Step 2: Use Workspaces</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init

<span class="hljs-comment"># Create dev workspace</span>
terraform workspace new dev
terraform plan
terraform apply -auto-approve

<span class="hljs-comment"># Create staging workspace</span>
terraform workspace new staging
terraform plan
terraform apply -auto-approve

<span class="hljs-comment"># Switch to dev</span>
terraform workspace select dev
terraform output

<span class="hljs-comment"># List all workspaces</span>
terraform workspace list

<span class="hljs-comment"># View state files</span>
ls -la terraform.tfstate.d/

<span class="hljs-comment"># Clean up</span>
terraform workspace select dev
terraform destroy -auto-approve

terraform workspace select staging
terraform destroy -auto-approve
terraform workspace select default

terraform workspace delete dev
terraform workspace delete staging
</code></pre>
<h2 id="heading-workspaces-vs-directories">⚖️ Workspaces vs Directories</h2>
<h3 id="heading-workspaces"><strong>Workspaces</strong></h3>
<p>✅ Same code for all environments ✅ Easy to switch between environments ✅ Less code duplication ❌ All environments must be similar. ❌ Easy to accidentally apply to wrong environment</p>
<h3 id="heading-separate-directories"><strong>Separate Directories</strong></h3>
<p>✅ Complete isolation ✅ Different configurations per environment ✅ Safer (harder to make mistakes) ❌ More code duplication ❌ Harder to keep in sync</p>
<h3 id="heading-recommendation"><strong>Recommendation</strong></h3>
<p>Use <strong>directories</strong> for production environments:</p>
<pre><code class="lang-plaintext">terraform/
├── dev/
├── staging/
└── prod/
</code></pre>
<p>Use <strong>workspaces</strong> for:</p>
<ul>
<li><p>Feature branches</p>
</li>
<li><p>Temporary environments</p>
</li>
<li><p>Testing</p>
</li>
<li><p>Development</p>
</li>
</ul>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Terraform workspaces</p>
</li>
<li><p>✅ Workspace commands</p>
</li>
<li><p>✅ Environment-specific configurations</p>
</li>
<li><p>✅ Workspaces vs directories</p>
</li>
</ul>
<h2 id="heading-tomorrow-terraform-import-amp-moving-resources">🚀 Tomorrow: Terraform Import &amp; Moving Resources</h2>
<hr />
<p><a target="_blank" href="day20.md">← Day 20: Remote State</a> | <a target="_blank" href="day23.md">Day 23: Terraform Import →</a></p>
]]></content:encoded></item><item><title><![CDATA[Day 20: Remote State & Backend Configuration]]></title><description><![CDATA[Welcome to Day 20 — the final day of Week 3 in our Terraform series!Today, we’ll dive into one of the most important production concepts in Terraform — remote state backends.You’ll learn how to safely store, share, and protect Terraform state files i...]]></description><link>https://stackopsdiary.site/day-20-remote-state-and-backend-configuration</link><guid isPermaLink="true">https://stackopsdiary.site/day-20-remote-state-and-backend-configuration</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Thu, 30 Oct 2025 12:30:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761816852137/f20840c9-2bf1-46b3-8572-2110e63660cf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to <strong>Day 20</strong> — the final day of <strong>Week 3</strong> in our Terraform series!<br />Today, we’ll dive into one of the <strong>most important production concepts</strong> in Terraform — <strong>remote state backends</strong>.<br />You’ll learn how to safely store, share, and protect Terraform state files in real-world team environments.</p>
<hr />
<h2 id="heading-learning-objectives">🎯 Learning Objectives</h2>
<p>By the end of today, you will be able to:</p>
<p>✅ Configure <strong>remote backends</strong> (S3 or Terraform Cloud)<br />✅ Enable <strong>state locking</strong> using DynamoDB<br />✅ Read data from <strong>remote state files</strong><br />✅ Manage <strong>multiple environments</strong> using isolated states<br />✅ Understand <strong>state isolation strategies</strong> and best practices</p>
<hr />
<h2 id="heading-what-is-terraform-state">🧩 What Is Terraform State?</h2>
<p>Terraform keeps track of your infrastructure resources in a file called <code>terraform.tfstate</code>.<br />This file acts as a <strong>record</strong> of what resources Terraform manages and their current attributes (like IDs, IPs, etc).</p>
<p>By default, this state is <strong>stored locally</strong> (on your machine).<br />That works fine for testing — but it’s <strong>risky</strong> for real production environments.</p>
<hr />
<h2 id="heading-problems-with-local-state">⚠️ Problems with Local State</h2>
<p>When Terraform state is stored locally, you face several issues:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Problem</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>❌ <strong>No collaboration</strong></td><td>Other team members can’t share or view the same state file</td></tr>
<tr>
<td>❌ <strong>No locking</strong></td><td>Two users running Terraform at the same time can corrupt the state</td></tr>
<tr>
<td>❌ <strong>No encryption</strong></td><td>Sensitive data like passwords or secrets may be stored in plain text</td></tr>
<tr>
<td>❌ <strong>Risk of loss</strong></td><td>If your laptop dies, your state is gone</td></tr>
<tr>
<td>❌ <strong>No versioning</strong></td><td>You can’t roll back to a previous state</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-why-use-remote-state">✅ Why Use Remote State?</h2>
<p>Remote backends solve all of these issues.<br />When you store your Terraform state remotely (for example, in <strong>S3</strong> or <strong>Terraform Cloud</strong>), you get:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Benefit</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>✅ <strong>Collaboration</strong></td><td>Shared state file accessible by all team members</td></tr>
<tr>
<td>✅ <strong>State Locking</strong></td><td>Prevents simultaneous updates</td></tr>
<tr>
<td>✅ <strong>Encryption</strong></td><td>Keeps your state file secure</td></tr>
<tr>
<td>✅ <strong>Versioning</strong></td><td>Easily roll back to previous versions</td></tr>
<tr>
<td>✅ <strong>Audit Trail</strong></td><td>Tracks who changed what and when</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-remote-state-with-aws-s3-recommended">☁️ Remote State with AWS S3 (Recommended)</h2>
<p>The most common backend setup for AWS-based projects is <strong>S3 + DynamoDB</strong>.</p>
<p>S3 stores the state file.<br />DynamoDB ensures <strong>state locking</strong>, preventing concurrent writes.</p>
<hr />
<h3 id="heading-basic-s3-backend-example">🧱 Basic S3 Backend Example</h3>
<pre><code class="lang-plaintext">terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "project/terraform.tfstate"
    region = "us-east-1"
  }
}
</code></pre>
<p>🧠 <strong>Explanation:</strong></p>
<ul>
<li><p><code>bucket</code>: The name of your S3 bucket that stores state.</p>
</li>
<li><p><code>key</code>: The path (like a folder) inside the bucket for the state file.</p>
</li>
<li><p><code>region</code>: AWS region of the bucket.</p>
</li>
</ul>
<hr />
<h3 id="heading-s3-backend-with-dynamodb-locking">🔒 S3 Backend with DynamoDB Locking</h3>
<pre><code class="lang-plaintext">terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "project/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
    workspace_key_prefix = "workspaces"
  }
}
</code></pre>
<p>🧠 <strong>Explanation:</strong></p>
<ul>
<li><p><code>encrypt = true</code> → Enables server-side encryption (AES-256).</p>
</li>
<li><p><code>dynamodb_table</code> → Uses DynamoDB to lock the state during apply.</p>
</li>
<li><p><code>workspace_key_prefix</code> → Keeps separate states for each workspace.</p>
</li>
</ul>
<hr />
<h2 id="heading-create-backend-infrastructure">🏗️ Create Backend Infrastructure</h2>
<p>Before configuring your backend, you must <strong>create the S3 bucket and DynamoDB table</strong> to store your state safely.</p>
<pre><code class="lang-plaintext"># backend-setup.tf
data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "terraform_state" {
  bucket = "terraform-state-${data.aws_caller_identity.current.account_id}"

  tags = {
    Name        = "Terraform State"
    Environment = "Production"
  }
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-state-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }

  tags = {
    Name        = "Terraform State Locks"
    Environment = "Production"
  }
}
</code></pre>
<p>🧠 <strong>Explanation:</strong></p>
<ul>
<li><p><strong>S3 Bucket</strong>: Stores Terraform state.</p>
</li>
<li><p><strong>Versioning</strong>: Keeps history of changes to allow rollback.</p>
</li>
<li><p><strong>Encryption</strong>: Protects your state file.</p>
</li>
<li><p><strong>Public Access Block</strong>: Prevents public exposure.</p>
</li>
<li><p><strong>DynamoDB Table</strong>: Manages state lock to prevent concurrent operations.</p>
</li>
</ul>
<hr />
<h2 id="heading-initialize-the-remote-backend">🔄 Initialize the Remote Backend</h2>
<h3 id="heading-migrating-from-local-to-remote"><strong>Migrating from Local to Remote</strong></h3>
<pre><code class="lang-plaintext"># Step 1: Add backend config

terraform {
  backend "s3" {
    bucket = "bucker-name
    key    = "prod/terraform.tfstate"
    region = "us-east-1"
  }
}

# Step 2: Initialize with migration
terraform init -migrate-state

# Step 3: Verify S3 file
aws s3 ls s3://bucket-name/prod/
</code></pre>
<hr />
<h3 id="heading-reconfiguring-backend"><strong>Reconfiguring Backend</strong></h3>
<pre><code class="lang-plaintext"># To change backend settings
terraform init -reconfigure

# To migrate existing state forcefully
terraform init -migrate-state -force-copy
</code></pre>
<hr />
<h2 id="heading-backend-configuration-patterns">🧠 Backend Configuration Patterns</h2>
<p>Terraform gives you multiple ways to provide backend configuration.</p>
<h3 id="heading-pattern-1-partial-configuration-recommended">🧩 Pattern 1: Partial Configuration (Recommended)</h3>
<p>Keep sensitive values outside your code.</p>
<p><a target="_blank" href="http://backend.tf"><code>backend.tf</code></a></p>
<pre><code class="lang-plaintext">terraform {
  backend "s3" {}
}
</code></pre>
<p><code>backend-config/prod.hcl</code></p>
<pre><code class="lang-plaintext">bucket         = "prod-terraform-state"
key            = "infrastructure/terraform.tfstate"
region         = "us-east-1"
encrypt        = true
dynamodb_table = "prod-terraform-locks"
</code></pre>
<p>Initialize using:</p>
<pre><code class="lang-plaintext">terraform init -backend-config=backend-config/prod.hcl
</code></pre>
<hr />
<h3 id="heading-pattern-2-environment-variables">🧩 Pattern 2: Environment Variables</h3>
<pre><code class="lang-plaintext">export TF_CLI_ARGS_init="-backend-config=bucket=my-state-bucket"
terraform init
</code></pre>
<hr />
<h3 id="heading-pattern-3-command-line-arguments">🧩 Pattern 3: Command Line Arguments</h3>
<pre><code class="lang-plaintext">terraform init \
  -backend-config="bucket=my-state-bucket" \
  -backend-config="key=project/terraform.tfstate" \
  -backend-config="region=us-east-1"
</code></pre>
<hr />
<h2 id="heading-state-isolation-strategies">🧱 State Isolation Strategies</h2>
<p>State isolation ensures different environments (dev, staging, prod) do not interfere with each other.</p>
<h3 id="heading-1-directory-based-isolation">1️⃣ Directory-Based Isolation</h3>
<pre><code class="lang-plaintext">terraform/
├── dev/
│   └── backend.tf  # key = "dev/terraform.tfstate"
├── staging/
│   └── backend.tf  # key = "staging/terraform.tfstate"
└── prod/
    └── backend.tf  # key = "prod/terraform.tfstate"
</code></pre>
<p>Each environment has its own backend file and state file.</p>
<hr />
<h3 id="heading-2-workspaces">2️⃣ Workspaces</h3>
<pre><code class="lang-plaintext">terraform {
  backend "s3" {
    bucket               = "terraform-state"
    key                  = "project.tfstate"
    workspace_key_prefix = "env"
    region               = "us-east-1"
  }
}
</code></pre>
<p>💡 Workspaces create automatically isolated state files:</p>
<pre><code class="lang-plaintext">s3://terraform-state/env/dev/project.tfstate
s3://terraform-state/env/prod/project.tfstate
</code></pre>
<pre><code class="lang-plaintext">terraform workspace new dev
terraform workspace select dev
</code></pre>
<hr />
<h2 id="heading-terraform-cloud-backend">☁️ Terraform Cloud Backend</h2>
<p>Terraform Cloud provides a fully managed remote backend with versioning, state locking, and collaboration out of the box.</p>
<h3 id="heading-basic-configuration"><strong>Basic Configuration</strong></h3>
<pre><code class="lang-plaintext">terraform {
  cloud {
    organization = "stackop"

    workspaces {
      name = "dev-workspace"
    }
  }
}
</code></pre>
<h3 id="heading-using-multiple-workspaces"><strong>Using Multiple Workspaces</strong></h3>
<pre><code class="lang-plaintext">terraform {
  cloud {
    organization = "stackops"

    workspaces {
      tags = ["networking", "production"]
    }
  }
}
</code></pre>
<hr />
<h2 id="heading-summary">🎉 Summary</h2>
<p>By now, you’ve learned how to:</p>
<p>✅ Configure and migrate to remote state<br />✅ Enable state locking with DynamoDB<br />✅ Organize environments using isolation strategies<br />✅ Use Terraform Cloud as an alternative backend</p>
<h2 id="heading-day-21-preview">🚀 Day 21 Preview</h2>
<p><strong>Day 21: Workspaces for Environment Management</strong></p>
<p>focuses on advanced techniques:</p>
<ul>
<li><p>Workspaces</p>
</li>
<li><p>Terraform import</p>
</li>
<li><p>Provisioners</p>
</li>
<li><p>Testing strategies</p>
</li>
<li><p>CI/CD integration</p>
</li>
<li><p>Security best practices</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="day19.md">← Day 19: Module Sources</a> | <a target="_blank" href="day22.md">Day 22: Workspaces →</a></p>
<hr />
<p><em>Remember: Remote state is essential for team collaboration and production deployments!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 19: Module Sources & Versioning]]></title><description><![CDATA[Welcome to Day 19!Today, we’ll dive deep into how to manage, version, and share Terraform modules effectively — one of the most important skills for creating scalable and maintainable infrastructure.
By the end of today, you’ll know exactly how to:✅ ...]]></description><link>https://stackopsdiary.site/day-19-module-sources-and-versioning</link><guid isPermaLink="true">https://stackopsdiary.site/day-19-module-sources-and-versioning</guid><category><![CDATA[Terraform]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Tue, 28 Oct 2025 12:30:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761646738820/101585ca-9d98-484c-9ed2-8607d7d58583.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to <strong>Day 19!</strong><br />Today, we’ll dive deep into <strong>how to manage, version, and share Terraform modules effectively</strong> — one of the most important skills for creating scalable and maintainable infrastructure.</p>
<p>By the end of today, you’ll know exactly how to:<br />✅ Use modules from different sources (local, Git, S3, etc.)<br />✅ Apply versioning best practices<br />✅ Manage dependencies between modules<br />✅ Publish and share your own reusable modules<br />✅ Upgrade modules safely in production</p>
<hr />
<h2 id="heading-todays-learning-objectives">🎯 Today’s Learning Objectives</h2>
<ul>
<li><p>Understand different <strong>Terraform module sources</strong></p>
</li>
<li><p>Learn <strong>semantic versioning</strong> and version constraints</p>
</li>
<li><p>Use modules from the <strong>Terraform Registry</strong></p>
</li>
<li><p>Handle <strong>dependencies</strong> between modules</p>
</li>
<li><p>Publish and <strong>upgrade</strong> your own Terraform modules safely</p>
</li>
</ul>
<hr />
<h2 id="heading-1-terraform-module-sources-explained">📚 1. Terraform Module Sources Explained</h2>
<p>Terraform allows you to use modules from various sources—from your local filesystem to GitHub, Bitbucket, S3, and even Terraform Registry.</p>
<p>Let’s go through each type with examples. 👇</p>
<hr />
<h3 id="heading-1-local-modules">🔹 <strong>1. Local Modules</strong></h3>
<p>These are modules stored locally on your computer or within your project directory.</p>
<pre><code class="lang-plaintext">module "vpc" {
  source = "./modules/vpc"  # Local relative path
}

module "network" {
  source = "../shared-modules/network"  # Parent directory
}

module "app" {
  source = "/absolute/path/to/module"   # Absolute path
}
</code></pre>
<p><strong>💡 Use case:</strong></p>
<ul>
<li><p>You’re developing custom modules for your project</p>
</li>
<li><p>You’re testing module changes quickly</p>
</li>
<li><p>You’re working in a single repo (monorepo)</p>
</li>
</ul>
<hr />
<h3 id="heading-2-terraform-registry-modules">🔹 <strong>2. Terraform Registry Modules</strong></h3>
<p>These are <strong>prebuilt community or official modules</strong> available on <a target="_blank" href="https://registry.terraform.io/">Terraform Registry</a>.</p>
<pre><code class="lang-plaintext">module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"
}
</code></pre>
<p><strong>Format:</strong><br /><code>&lt;NAMESPACE&gt;/&lt;MODULE_NAME&gt;/&lt;PROVIDER&gt;</code></p>
<p><strong>💡 Use case:</strong></p>
<ul>
<li><p>Want reliable, production-tested modules</p>
</li>
<li><p>Want to save time and follow AWS best practices</p>
</li>
<li><p>Example: <code>terraform-aws-modules/vpc/aws</code>, <code>terraform-aws-modules/eks/aws</code></p>
</li>
</ul>
<hr />
<h3 id="heading-3-github-modules">🔹 <strong>3. GitHub Modules</strong></h3>
<p>You can pull modules directly from GitHub via HTTPS, SSH, or specific version references.</p>
<pre><code class="lang-plaintext"># HTTPS
module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc"
}

# Specific version (tag or branch)
module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.1.2"
}

# Subdirectory
module "example" {
  source = "github.com/hashicorp/terraform-aws-consul//modules/consul-cluster?ref=v0.11.0"
}
</code></pre>
<p><strong>💡 Use case:</strong></p>
<ul>
<li><p>Using custom internal modules hosted on GitHub</p>
</li>
<li><p>Need fine-grained version control via Git tags</p>
</li>
<li><p>Want to contribute or fork existing modules</p>
</li>
</ul>
<hr />
<h3 id="heading-4-generic-git-repositories">🔹 <strong>4. Generic Git Repositories</strong></h3>
<p>You can use any Git server (GitLab, private repo, etc.):</p>
<pre><code class="lang-plaintext">module "vpc" {
  source = "git::https://gitlab-stackopsdiary.site/vpc.git?ref=v1.2.0"
}
</code></pre>
<p><strong>💡 Use case:</strong></p>
<ul>
<li>Enterprise teams hosting private infrastructure modules</li>
</ul>
<hr />
<h3 id="heading-5-s3-and-http-sources">🔹 <strong>5. S3, and HTTP Sources</strong></h3>
<p>You can also store your modules as <strong>ZIP archives</strong> in cloud storage or URLs.</p>
<p><strong>S3 Example:</strong></p>
<pre><code class="lang-plaintext">module "vpc" {
  source = "s3::https://s3-eu-west-1.amazonaws.com/company-modules/vpc-1.2.3.zip"
}
</code></pre>
<p><strong>💡 Use case:</strong></p>
<ul>
<li><p>Hosting private modules for secure internal distribution</p>
</li>
<li><p>Integrating modules in CI/CD pipelines</p>
</li>
</ul>
<hr />
<h2 id="heading-2-module-versioning-semver-explained">🏷️ 2. Module Versioning (SemVer Explained)</h2>
<h3 id="heading-semantic-versioning-semver">🧩 Semantic Versioning (SemVer)</h3>
<p>Terraform follows <strong>Semantic Versioning (MAJOR.MINOR.PATCH)</strong>.</p>
<pre><code class="lang-plaintext">v1.2.3
 │ │ │
 │ │ └─ PATCH: Bug fixes (no breaking changes)
 │ └─── MINOR: Backward-compatible features
 └───── MAJOR: Breaking changes
</code></pre>
<p>So if a module goes from <strong>v2.3.1 → v3.0.0</strong>, it likely introduces breaking changes.</p>
<hr />
<h3 id="heading-version-constraints">⚙️ Version Constraints</h3>
<p>You can define how Terraform should handle module versions:</p>
<pre><code class="lang-plaintext">module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~&gt; 5.0" # Allows 5.x versions but not 6.x
}
</code></pre>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Constraint</td><td>Meaning</td></tr>
</thead>
<tbody>
<tr>
<td><code>= 5.1.2</code></td><td>Exact version</td></tr>
<tr>
<td><code>&gt;= 5.0.0</code></td><td>Any version 5.0.0 or newer</td></tr>
<tr>
<td><code>~&gt; 5.0</code></td><td>\&gt;=5.0, &lt;6.0</td></tr>
<tr>
<td><code>~&gt; 5.1.0</code></td><td>\&gt;=5.1.0, &lt;5.2.0</td></tr>
<tr>
<td><code>!= 5.1.0</code></td><td>Exclude version 5.1.0</td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-best-practices">✅ Best Practices</h3>
<p><strong>Do:</strong></p>
<ul>
<li><p>Always pin versions for production</p>
</li>
<li><p>Use <code>~&gt;</code> for minor auto-updates</p>
</li>
<li><p>Document module version requirements</p>
</li>
</ul>
<p><strong>Don’t:</strong></p>
<ul>
<li><p>Leave version unpinned</p>
</li>
<li><p>Use <code>latest</code> without testing</p>
</li>
<li><p>Jump to major versions blindly</p>
</li>
</ul>
<hr />
<h2 id="heading-3-using-terraform-registry-modules">🌐 3. Using Terraform Registry Modules</h2>
<p>The Terraform Registry is like a module marketplace.</p>
<h3 id="heading-steps">Steps:</h3>
<ol>
<li><p>Go to <a target="_blank" href="https://registry.terraform.io/">registry.terraform.io</a></p>
</li>
<li><p>Search, e.g., “aws vpc.”</p>
</li>
<li><p>Review downloads, provider support, last update, and documentation</p>
</li>
</ol>
<h3 id="heading-example-vpc-security-group-rds">Example: VPC + Security Group + RDS</h3>
<pre><code class="lang-plaintext">module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~&gt; 5.0"
  name    = "my-vpc"
  cidr    = "10.0.0.0/16"
}
</code></pre>
<pre><code class="lang-plaintext">module "web_sg" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "~&gt; 5.0"
  vpc_id  = module.vpc.vpc_id
}
</code></pre>
<pre><code class="lang-plaintext">module "db" {
  source  = "terraform-aws-modules/rds/aws"
  version = "~&gt; 6.0"
  engine  = "postgres"
}
</code></pre>
<p>These modules are <strong>battle-tested</strong>, frequently updated, and follow AWS best practices.</p>
<hr />
<h2 id="heading-4-managing-module-dependencies">🔄 4. Managing Module Dependencies</h2>
<p>Modules often depend on each other — e.g., your EC2 needs a VPC ID.</p>
<h3 id="heading-implicit-dependency">🔸 Implicit Dependency</h3>
<p>Terraform automatically detects it when outputs are referenced:</p>
<pre><code class="lang-plaintext">module "vpc" {
  source = "./modules/vpc"
}

module "ec2" {
  source = "./modules/ec2"
  vpc_id = module.vpc.vpc_id  # Dependency created here
}
</code></pre>
<h3 id="heading-explicit-dependency">🔸 Explicit Dependency</h3>
<p>You can also manually define dependencies:</p>
<pre><code class="lang-plaintext">module "monitoring" {
  source = "./modules/monitoring"
  depends_on = [module.vpc, module.ec2]
}
</code></pre>
<hr />
<h2 id="heading-5-publishing-your-own-module">📦 5. Publishing Your Own Module</h2>
<p>When your module is reusable, you can share it with others!</p>
<h3 id="heading-structure-example">Structure Example</h3>
<pre><code class="lang-plaintext">terraform-aws-mymodule/
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── examples/
└── LICENSE
</code></pre>
<h3 id="heading-steps-1">Steps:</h3>
<ol>
<li><p><strong>Create &amp; tag version:</strong></p>
<pre><code class="lang-plaintext"> git tag -a v1.0.0 -m "Initial release"
 git push origin v1.0.0
</code></pre>
</li>
<li><p><strong>Publish</strong> on <a target="_blank" href="https://registry.terraform.io/">Terraform Registry</a></p>
</li>
<li><p>Follow naming:<br /> <code>terraform-&lt;PROVIDER&gt;-&lt;MODULE_NAME&gt;</code> (e.g. <code>terraform-aws-vpc</code>)</p>
</li>
</ol>
<hr />
<h2 id="heading-6-hands-on-labusing-multiple-module-sources">🧪 6. Hands-On Lab—Using Multiple Module Sources</h2>
<p><strong>Goal:</strong> Combine Registry, GitHub, and local modules.</p>
<pre><code class="lang-plaintext">mkdir terraform-module-sources-lab
cd terraform-module-sources-lab
</code></pre>
<p><code>main.tf</code> (abridged):</p>
<pre><code class="lang-plaintext">provider "aws" {
  region = "us-east-1"
  profile = "stackops"
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~&gt; 6.0"

  name = "stackop-main"
  cidr = "10.0.0.0/16"
}

module "security_group_web" {
  source = "github.com/terraform-aws-modules/terraform-aws-security-group?ref=v5.1.0"
  vpc_id = module.vpc.vpc_id
  name = "stackops-web"
}
</code></pre>
<p>Then:</p>
<pre><code class="lang-plaintext">terraform init
terraform plan
terraform apply
terraform destroy
</code></pre>
<p>You’ll see modules downloaded in <code>.terraform/modules/</code>.</p>
<hr />
<h2 id="heading-7-module-upgrade-strategy">🔧 7. Module Upgrade Strategy</h2>
<p>When new versions are released:</p>
<ol>
<li><p><strong>Check for updates</strong></p>
<pre><code class="lang-plaintext"> terraform init -upgrade
 git diff .terraform.lock.hcl
</code></pre>
</li>
<li><p><strong>Test in dev</strong><br /> Update the version in yours, apply, and validate.</p>
</li>
<li><p><strong>Review changelog</strong><br /> Always read release notes for breaking changes.</p>
</li>
<li><p><strong>Gradual rollout:</strong><br /> Dev → QA → Staging → Production.</p>
</li>
</ol>
<hr />
<h2 id="heading-day-19-summary">📝 <strong>Day 19 Summary</strong></h2>
<p>Today, you learned how to:</p>
<p>✅ Use Terraform modules from local, Git, S3, and Registry sources<br />✅ Apply semantic versioning and constraints<br />✅ Manage module dependencies<br />✅ Publish your own modules<br />✅ Upgrade modules safely without breaking production</p>
<hr />
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 20: Remote State &amp; Backend Configuration</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Configure S3 backend</p>
</li>
<li><p>Implement state locking</p>
</li>
<li><p>Use remote state data sources</p>
</li>
<li><p>Manage multiple state files</p>
</li>
<li><p>Implement state isolation strategies</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-18-creating-your-own-reusable-modules">← Day 18: Custom Modules</a> | <a target="_blank" href="day20.md">Day 20: Remote State &amp; Backend →</a></p>
<hr />
<p><em>Remember: Proper versioning ensures stable, predictable infrastructure deployments!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 18: Creating Your Own Reusable Modules]]></title><description><![CDATA[Welcome to Day 18! Today, you’ll take that skill to the next level — by learning how to design and build your own reusable, production-ready Terraform modules that can scale across teams, projects, and environments.
If you want to work like a DevOps ...]]></description><link>https://stackopsdiary.site/day-18-creating-your-own-reusable-modules</link><guid isPermaLink="true">https://stackopsdiary.site/day-18-creating-your-own-reusable-modules</guid><category><![CDATA[Terraform]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Mon, 27 Oct 2025 12:30:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761558145616/7a27907b-2d3c-4178-a4b1-9c62966b8af3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to <strong>Day 18!</strong> Today, you’ll take that skill to the <strong>next level</strong> — by learning how to <strong>design and build your own reusable, production-ready Terraform modules</strong> that can scale across teams, projects, and environments.</p>
<p>If you want to work like a DevOps or cloud engineer in a real-world team, mastering this topic is absolutely essential.</p>
<hr />
<h2 id="heading-todays-learning-goals">🎯 Today’s Learning Goals</h2>
<p>By the end of this lesson, you’ll be able to:</p>
<p>✅ Design <strong>production-quality Terraform modules</strong> that follow best practices<br />✅ Handle <strong>optional and conditional resources</strong> inside modules<br />✅ Build <strong>modular and composable</strong> infrastructure<br />✅ Make your modules <strong>flexible and reusable</strong> through inputs, locals, and outputs<br />✅ Use <strong>advanced module design patterns</strong> used by large organizations</p>
<hr />
<h2 id="heading-terraform-module-design-principles">🎨 Terraform Module Design Principles</h2>
<p>When creating reusable modules, always design with <strong>clarity, flexibility, and simplicity</strong> in mind.<br />Here are the core principles used by top Terraform practitioners.</p>
<hr />
<h3 id="heading-1-single-responsibility-principle"><strong>1. Single Responsibility Principle</strong></h3>
<blockquote>
<p>Each module should do <strong>one thing well</strong> and be easy to understand in isolation.</p>
</blockquote>
<p>This is similar to how a function in programming should only have one purpose.</p>
<hr />
<h4 id="heading-bad-example-god-module-too-big">❌ <strong>Bad Example — “God Module” (Too Big)</strong></h4>
<pre><code class="lang-plaintext">modules/infrastructure/
├── vpc, subnets, instances, databases, load balancers...
</code></pre>
<p>This “mega-module” mixes everything — networking, compute, database, and load balancers.<br />It’s hard to maintain, reuse, or debug because a small change can break unrelated components.</p>
<hr />
<h4 id="heading-good-example-focused-modules">✅ <strong>Good Example — Focused Modules</strong></h4>
<pre><code class="lang-plaintext">modules/
├── networking/       # Handles VPC, subnets, route tables
├── compute/          # Manages EC2 instances or Auto Scaling Groups
├── database/         # Creates RDS or DynamoDB
└── load-balancer/    # Manages ALB or NLB
</code></pre>
<p>Now, each module has a <strong>single responsibility</strong>, making it:</p>
<ul>
<li><p>Easier to test</p>
</li>
<li><p>Easier to reuse</p>
</li>
<li><p>Easier to maintain and version</p>
</li>
</ul>
<hr />
<h3 id="heading-2-composable-design-modules-working-together"><strong>2. Composable Design — Modules Working Together</strong></h3>
<blockquote>
<p>Think of modules like Lego blocks — small, independent, and connectable.</p>
</blockquote>
<p>Each module should expose outputs that can be consumed by another module.</p>
<hr />
<h4 id="heading-example">Example:</h4>
<pre><code class="lang-plaintext"># Create a network (VPC and subnets)
module "network" {
  source = "./modules/networking"
}

# Compute module (EC2) depends on VPC outputs
module "compute" {
  source = "./modules/compute"

  vpc_id     = module.network.vpc_id
  subnet_ids = module.network.private_subnet_ids
}

# Database module reuses same network data
module "database" {
  source = "./modules/database"

  vpc_id     = module.network.vpc_id
  subnet_ids = module.network.database_subnet_ids
}
</code></pre>
<p>Here:</p>
<ul>
<li><p><a target="_blank" href="http://module.network"><code>module.network</code></a> → provides foundational infrastructure</p>
</li>
<li><p><code>module.compute</code> and <code>module.database</code> → consume network outputs</p>
</li>
<li><p>Each module can be updated independently</p>
</li>
</ul>
<hr />
<h3 id="heading-3-sensible-defaults"><strong>3. Sensible Defaults</strong></h3>
<blockquote>
<p>Always define defaults for common cases so users can use the module with minimal setup.</p>
</blockquote>
<p>When building reusable modules, your goal is to make them <strong>simple for beginners</strong> but <strong>flexible for experts</strong>.</p>
<hr />
<h4 id="heading-example-1">Example:</h4>
<pre><code class="lang-plaintext">variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"  # Common low-cost instance
}

variable "monitoring" {
  description = "Enable detailed monitoring"
  type        = bool
  default     = false       # Optional feature, off by default
}
</code></pre>
<p>Now, your module can be used <strong>without</strong> providing extra inputs:</p>
<pre><code class="lang-plaintext">module "web" {
  source = "./modules/compute"
}
</code></pre>
<p>Or overridden easily when needed:</p>
<pre><code class="lang-plaintext">module "web" {
  source        = "./modules/compute"
  instance_type = "t3.medium"
  monitoring    = true
}
</code></pre>
<hr />
<h3 id="heading-4-input-validation-guard-against-mistakes"><strong>4. Input Validation — Guard Against Mistakes</strong></h3>
<blockquote>
<p>Validation helps catch invalid configurations early before Terraform even runs.</p>
</blockquote>
<p>Without validation, a user could accidentally input something wrong — like <code>instance_count = 0</code> — and cause unexpected errors.</p>
<hr />
<h4 id="heading-example-2">Example:</h4>
<pre><code class="lang-plaintext">variable "environment" {
  type = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "instance_count" {
  type = number

  validation {
    condition     = var.instance_count &gt; 0 &amp;&amp; var.instance_count &lt;= 10
    error_message = "Instance count must be between 1 and 10."
  }
}
</code></pre>
<p>🧠 <strong>Tip:</strong><br />Validations help prevent “human error” and enforce team-wide standards.</p>
<hr />
<h2 id="heading-advanced-module-patterns">🧩 Advanced Module Patterns</h2>
<p>Now let’s dive into <strong>advanced design patterns</strong> that make modules dynamic, flexible, and smart.</p>
<hr />
<h3 id="heading-pattern-1-optional-resources-using-count"><strong>🧱 Pattern 1: Optional Resources (Using</strong> <code>count</code>)</h3>
<blockquote>
<p>Sometimes, you want certain resources (like bastion hosts or ALBs) to be <strong>optional</strong> — depending on configuration.</p>
</blockquote>
<hr />
<h4 id="heading-example-3">Example:</h4>
<p><a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a></p>
<pre><code class="lang-plaintext">variable "create_bastion" {
  description = "Whether to create a bastion host"
  type        = bool
  default     = false
}

variable "create_alb" {
  description = "Whether to create an Application Load Balancer"
  type        = bool
  default     = true
}
</code></pre>
<p><a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></p>
<pre><code class="lang-plaintext">resource "aws_instance" "bastion" {
  count = var.create_bastion ? 1 : 0

  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
}

resource "aws_lb" "app" {
  count = var.create_alb ? 1 : 0

  name               = "${var.name}-alb"
  load_balancer_type = "application"
}
</code></pre>
<p>This makes your module <strong>conditional</strong> — creating resources only when needed.</p>
<hr />
<h3 id="heading-pattern-2-feature-flags-turn-features-on-or-off"><strong>🚩 Pattern 2: Feature Flags (Turn Features On or Off)</strong></h3>
<p>Feature flags allow users to <strong>toggle specific features</strong> of your module without changing the logic.</p>
<hr />
<h4 id="heading-example-4">Example:</h4>
<pre><code class="lang-plaintext">variable "features" {
  description = "Feature toggles for the module"
  type = object({
    auto_scaling = bool
    monitoring   = bool
    backup       = bool
  })
  default = {
    auto_scaling = false
    monitoring   = false
    backup       = false
  }
}

resource "aws_autoscaling_group" "this" {
  count = var.features.auto_scaling ? 1 : 0
  # ...
}

resource "aws_cloudwatch_dashboard" "this" {
  count = var.features.monitoring ? 1 : 0
  # ...
}
</code></pre>
<p>Users can now control features easily:</p>
<pre><code class="lang-plaintext">module "app" {
  source = "./modules/app"

  features = {
    auto_scaling = true
    monitoring   = true
    backup       = false
  }
}
</code></pre>
<hr />
<h3 id="heading-pattern-3-environment-specific-behavior"><strong>🏗️ Pattern 3: Environment-Specific Behavior</strong></h3>
<blockquote>
<p>Use environment names (<code>dev</code>, <code>staging</code>, <code>prod</code>) to dynamically change resource settings.</p>
</blockquote>
<p>This is one of the most <strong>realistic and powerful</strong> Terraform techniques — commonly used in enterprise setups.</p>
<hr />
<h4 id="heading-example-5">Example:</h4>
<pre><code class="lang-plaintext">variable "environment" {
  description = "Environment name (dev, staging, prod)"
  type        = string
}

locals {
  is_production = var.environment == "prod"

  instance_config = {
    dev = {
      type  = "t2.micro"
      count = 1
    }
    staging = {
      type  = "t2.small"
      count = 2
    }
    prod = {
      type  = "t3.medium"
      count = 3
    }
  }

  selected_config = local.instance_config[var.environment]
}

resource "aws_instance" "app" {
  count         = local.selected_config.count
  instance_type = local.selected_config.type
  monitoring    = local.is_production
}
</code></pre>
<p>✅ <strong>Result:</strong></p>
<ul>
<li><p>Dev → 1 small instance</p>
</li>
<li><p>Staging → 2 medium instances</p>
</li>
<li><p>Prod → 3 large instances with monitoring enabled</p>
</li>
</ul>
<p>This makes a single module automatically adapt to different environments.</p>
<hr />
<h3 id="heading-pattern-4-flexible-tagging-system"><strong>🏷️ Pattern 4: Flexible Tagging System</strong></h3>
<blockquote>
<p>Consistent tagging helps with cost tracking, ownership, and automation.<br />You can merge “standard tags” with user-provided ones.</p>
</blockquote>
<hr />
<h4 id="heading-example-6">Example:</h4>
<pre><code class="lang-plaintext">variable "tags" {
  description = "Additional custom tags"
  type        = map(string)
  default     = {}
}

variable "name" {
  description = "Resource name"
  type        = string
}

locals {
  common_tags = merge(
    {
      Name        = var.name
      ManagedBy   = "Terraform"
      Module      = "app"
    },
    var.tags  # Allow users to override or add
  )
}

resource "aws_instance" "this" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
  tags          = local.common_tags
}
</code></pre>
<p>✅ <strong>Resulting Tags Example:</strong></p>
<pre><code class="lang-plaintext">tags = {
  Name        = "web-server"
  ManagedBy   = "Terraform"
  Module      = "app"
  Environment = "staging"  # Custom user tag
}
</code></pre>
<hr />
<h2 id="heading-hands-on-practice-recommended">🧪 Hands-On Practice (Recommended)</h2>
<hr />
<p>We'll use:</p>
<ul>
<li><p>✅ Conditional logic (<code>locals</code>)</p>
</li>
<li><p>✅ Clean module structure</p>
</li>
<li><p>✅ Simple <a target="_blank" href="http://user-data.sh"><code>user-data.sh</code></a> to run a web server</p>
</li>
<li><p>✅ A reusable module you can plug anywhere</p>
</li>
</ul>
<hr />
<h2 id="heading-goal">🌟 Goal</h2>
<p>A Terraform project that:</p>
<ul>
<li><p>Creates a <strong>VPC</strong> (basic networking)</p>
</li>
<li><p>Creates <strong>1 EC2 instance</strong></p>
</li>
<li><p>Automatically configures instance type based on environment (<code>dev</code>, <code>qa</code>, <code>staging</code>, <code>prod</code>)</p>
</li>
<li><p>Outputs the <strong>public IP</strong> so you can open it in your browser</p>
</li>
</ul>
<hr />
<h2 id="heading-folder-structure">📂 Folder Structure</h2>
<pre><code class="lang-plaintext">terraform-env-ec2-lab/
├── main.tf
├── outputs.tf
├── providers.tf
└── modules/
    ├── vpc/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── web-app/
        ├── main.tf
        ├── variables.tf
        ├── locals.tf
        ├── outputs.tf
        └── templates/
            └── user-data.sh
</code></pre>
<hr />
<h2 id="heading-1-vpc-module">🌐 1. VPC Module</h2>
<h3 id="heading-modulesvpcvariablestfhttpvariablestf"><code>modules/vpc/</code><a target="_blank" href="http://variables.tf"><code>variables.tf</code></a></h3>
<pre><code class="lang-plaintext">variable "vpc_name" {
  description = "VPC name"
  type        = string
}

variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
}

variable "azs" {
  description = "Availability zones"
  type        = list(string)
}

variable "public_subnets" {
  description = "Public subnet CIDRs"
  type        = list(string)
}

variable "tags" {
  description = "Additional tags"
  type        = map(string)
  default     = {}
}
</code></pre>
<hr />
<h3 id="heading-modulesvpcmaintfhttpmaintf"><code>modules/vpc/</code><a target="_blank" href="http://main.tf"><code>main.tf</code></a></h3>
<pre><code class="lang-plaintext">resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = merge(
    {
      Name = var.vpc_name
    },
    var.tags
  )
}

resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id
  tags = {
    Name = "${var.vpc_name}-igw"
  }
}

resource "aws_subnet" "public" {
  count                   = length(var.public_subnets)
  vpc_id                  = aws_vpc.this.id
  cidr_block              = var.public_subnets[count.index]
  map_public_ip_on_launch = true
  availability_zone       = var.azs[count.index % length(var.azs)]

  tags = {
    Name = "${var.vpc_name}-public-${count.index + 1}"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.this.id
  tags = {
    Name = "${var.vpc_name}-public-rt"
  }
}

resource "aws_route" "public_internet" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.this.id
}

resource "aws_route_table_association" "public" {
  count          = length(aws_subnet.public)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}
</code></pre>
<hr />
<h3 id="heading-modulesvpcoutputstfhttpoutputstf"><code>modules/vpc/</code><a target="_blank" href="http://outputs.tf"><code>outputs.tf</code></a></h3>
<pre><code class="lang-plaintext">output "vpc_id" {
  value = aws_vpc.this.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}
</code></pre>
<hr />
<h2 id="heading-2-environment-aware-ec2-module">💻 2. Environment-Aware EC2 Module</h2>
<h3 id="heading-modulesweb-appvariablestfhttpvariablestf"><code>modules/web-app/</code><a target="_blank" href="http://variables.tf"><code>variables.tf</code></a></h3>
<pre><code class="lang-plaintext">variable "name" {
  description = "Application name"
  type        = string
}

variable "environment" {
  description = "Environment name (dev, qa, staging, prod)"
  type        = string

  validation {
    condition     = contains(["dev", "qa", "staging", "prod"], var.environment)
    error_message = "Environment must be one of: dev, qa, staging, prod."
  }
}

variable "vpc_id" {
  description = "VPC ID"
  type        = string
}

variable "subnet_id" {
  description = "Subnet ID for EC2 instance"
  type        = string
}

variable "key_name" {
  description = "Key pair name for SSH access"
  type        = string
}

variable "allowed_cidr_blocks" {
  description = "CIDR blocks allowed to access EC2"
  type        = list(string)
  default     = ["0.0.0.0/0"]
}

variable "tags" {
  description = "Additional resource tags"
  type        = map(string)
  default     = {}
}
</code></pre>
<hr />
<h3 id="heading-modulesweb-applocalstfhttplocalstf"><code>modules/web-app/</code><a target="_blank" href="http://locals.tf"><code>locals.tf</code></a></h3>
<pre><code class="lang-plaintext">locals {
  # Define instance configuration based on environment
  instance_config = {
    dev = {
      type = "t2.micro"
    }
    qa = {
      type = "t3.medium"
    }
    staging = {
      type = "t3.large"
    }
    prod = {
      type = "t3.xlarge"
    }
  }

  instance_type = lookup(local.instance_config[var.environment], "type", "t2.micro")

  name_prefix = "${var.name}-${var.environment}"

  common_tags = merge(
    {
      Name        = local.name_prefix
      Environment = var.environment
      ManagedBy   = "Terraform"
      Module      = "web-app"
    },
    var.tags
  )
}
</code></pre>
<hr />
<h3 id="heading-modulesweb-appmaintfhttpmaintf"><code>modules/web-app/</code><a target="_blank" href="http://main.tf"><code>main.tf</code></a></h3>
<pre><code class="lang-plaintext">data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# Security Group
resource "aws_security_group" "this" {
  name        = "${local.name_prefix}-sg"
  description = "Allow HTTP and SSH"
  vpc_id      = var.vpc_id

  ingress {
    description = "Allow HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = var.allowed_cidr_blocks
  }

  ingress {
    description = "Allow SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = var.allowed_cidr_blocks
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = local.common_tags
}

# EC2 Instance
resource "aws_instance" "this" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = local.instance_type
  subnet_id              = var.subnet_id
  key_name               = var.key_name
  vpc_security_group_ids = [aws_security_group.this.id]
  associate_public_ip_address = true

  user_data = templatefile("${path.module}/templates/user-data.sh", {
    app_name    = var.name
    environment = var.environment
  })

  tags = local.common_tags
}
</code></pre>
<hr />
<h3 id="heading-modulesweb-appoutputstfhttpoutputstf"><code>modules/web-app/</code><a target="_blank" href="http://outputs.tf"><code>outputs.tf</code></a></h3>
<pre><code class="lang-plaintext">output "instance_id" {
  value = aws_instance.this.id
}

output "public_ip" {
  value = aws_instance.this.public_ip
}

output "private_ip" {
  value = aws_instance.this.private_ip
}

output "instance_type" {
  value = aws_instance.this.instance_type
}
</code></pre>
<hr />
<h3 id="heading-modulesweb-apptemplatesuser-datashhttpuser-datash"><code>modules/web-app/templates/</code><a target="_blank" href="http://user-data.sh"><code>user-data.sh</code></a></h3>
<pre><code class="lang-plaintext">#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd

cat &gt; /var/www/html/index.html &lt;&lt;EOF
&lt;html&gt;
  &lt;head&gt;&lt;title&gt;${app_name}&lt;/title&gt;&lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;${app_name}&lt;/h1&gt;
    &lt;p&gt;Environment: ${environment}&lt;/p&gt;
    &lt;p&gt;Instance Type: $(curl -s http://169.254.169.254/latest/meta-data/instance-type)&lt;/p&gt;
    &lt;p&gt;Instance ID: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)&lt;/p&gt;
    &lt;p&gt;Availability Zone: $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
EOF
</code></pre>
<hr />
<h2 id="heading-3-root-configuration">🧩 3. Root Configuration</h2>
<h3 id="heading-maintfhttpmaintf"><a target="_blank" href="http://main.tf"><code>main.tf</code></a></h3>
<pre><code class="lang-plaintext"># VPC module
module "vpc" {
  source = "./modules/vpc"

  vpc_name       = "env-vpc"
  vpc_cidr       = "10.0.0.0/16"
  azs            = ["us-east-1a", "us-east-1b"]
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]

  tags = {
    Project = "env-based-ec2"
  }
}

# Dev Environment EC2
module "dev_app" {
  source = "./modules/web-app"

  name        = "webapp"
  environment = "dev"
  vpc_id      = module.vpc.vpc_id
  subnet_id   = module.vpc.public_subnet_ids[0]
  key_name    = "stackops" # replace this

  tags = {
    Environment = "Dev"
    Team        = "Platform"
    Owner       = "StackOps"
  }
}

# QA Environment EC2
module "qa_app" {
  source = "./modules/web-app"

  name        = "webapp"
  environment = "qa"
  vpc_id      = module.vpc.vpc_id
  subnet_id   = module.vpc.public_subnet_ids[1]
  key_name    = "stackops" # replace this

  tags = {
    Environment = "QA"
    Team        = "Platform"
    Owner       = "StackOps"
  }
}

# Prod Environment EC2
module "prod_app" {
  source = "./modules/web-app"

  name        = "webapp"
  environment = "prod"
  vpc_id      = module.vpc.vpc_id
  subnet_id   = module.vpc.public_subnet_ids[0]
  key_name    = "stackops" # replace this

  tags = {
    Environment = "production"
    Team        = "Platform"
    Owner       = "StackOps"
  }
}
</code></pre>
<h3 id="heading-providerstfhttpmaintf"><a target="_blank" href="http://main.tf"><code>providers.tf</code></a></h3>
<pre><code class="lang-plaintext">terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region  = "us-east-1"
  profile = "stackops"
}
</code></pre>
<h3 id="heading-outputstfhttpmaintf"><a target="_blank" href="http://main.tf"><code>outputs.tf</code></a></h3>
<pre><code class="lang-plaintext">output "dev_public_ip" {
  value = module.dev_app.public_ip
}

output "qa_public_ip" {
  value = module.qa_app.public_ip
}

output "prod_public_ip" {
  value = module.prod_app.public_ip
}
</code></pre>
<hr />
<h2 id="heading-4-run">4. Run</h2>
<pre><code class="lang-plaintext">terraform fmt
terraform init
terraform validate
terraform plan
terraform apply
</code></pre>
<p>✅ Terraform will:</p>
<ul>
<li><p>Create a VPC</p>
</li>
<li><p>Deploy <strong>3 EC2 instances</strong> (dev, qa, prod)</p>
</li>
<li><p>Each instance will use the right instance type:</p>
<ul>
<li><p>dev → t2.micro</p>
</li>
<li><p>qa → t3.medium</p>
</li>
<li><p>staging → t3.large (if added)</p>
</li>
<li><p>prod → t3.xlarge</p>
</li>
</ul>
</li>
</ul>
<p>Then you’ll get outputs like</p>
<pre><code class="lang-plaintext">dev_public_ip = 3.87.122.14
qa_public_ip = 18.205.33.19
prod_public_ip = 54.165.211.75
</code></pre>
<hr />
<h2 id="heading-test-in-browser">🌐 Test in Browser</h2>
<p>Open in your browser:</p>
<pre><code class="lang-plaintext">http://&lt;dev_public_ip&gt;
http://&lt;qa_public_ip&gt;
http://&lt;prod_public_ip&gt;
</code></pre>
<p>Each page shows:</p>
<ul>
<li><p>Environment name</p>
</li>
<li><p>Instance type</p>
</li>
<li><p>Instance ID and AZ</p>
</li>
</ul>
<hr />
<h2 id="heading-cleanup">🧹 Cleanup</h2>
<pre><code class="lang-plaintext">terraform destroy -auto-approve
</code></pre>
<hr />
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Module design principles</p>
</li>
<li><p>✅ Optional and conditional resources</p>
</li>
<li><p>✅ Feature flags pattern</p>
</li>
<li><p>✅ Environment-specific behavior</p>
</li>
<li><p>✅ Production-ready module structure</p>
</li>
<li><p>✅ Module composition</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 19: Module Sources &amp; Versioning</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Publish modules to registries</p>
</li>
<li><p>Version modules properly</p>
</li>
<li><p>Use remote module sources</p>
</li>
<li><p>Manage module dependencies</p>
</li>
<li><p>Implement module upgrades</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-17-introduction-to-terraform-modules">← Day 17: Introduction to Modules</a> | <a target="_blank" href="day19.md">Day 19: Module Sources →</a></p>
<hr />
<p><em>Remember: Well-designed modules are reusable, flexible, and easy to understand!</em></p>
]]></content:encoded></item><item><title><![CDATA[🚀 Day 17: Introduction to Terraform Modules]]></title><description><![CDATA[Welcome to Day 17 of your Terraform learning journey!Today, you’re going to explore one of the most powerful and essential features of Terraform — modules.
Modules allow you to organize, reuse, and simplify your infrastructure code. Once you understa...]]></description><link>https://stackopsdiary.site/day-17-introduction-to-terraform-modules</link><guid isPermaLink="true">https://stackopsdiary.site/day-17-introduction-to-terraform-modules</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Fri, 24 Oct 2025 12:30:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761304911593/60510606-be9c-44a6-9f57-a96bca6c4ac7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to <strong>Day 17</strong> of your Terraform learning journey!<br />Today, you’re going to explore one of the most powerful and essential features of Terraform — <strong>modules</strong>.</p>
<p>Modules allow you to <strong>organize</strong>, <strong>reuse</strong>, and <strong>simplify</strong> your infrastructure code. Once you understand modules, you’ll be able to build scalable, clean, and production-grade Terraform configurations.</p>
<hr />
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<p>By the end of this lesson, you will be able to:</p>
<ul>
<li><p>✅ Understand what Terraform modules are and why they’re so powerful</p>
</li>
<li><p>✅ Learn how modules are structured and organized</p>
</li>
<li><p>✅ Use public modules from the <strong>Terraform Registry</strong></p>
</li>
<li><p>✅ Pass data between modules using <strong>input variables</strong> and <strong>outputs</strong></p>
</li>
<li><p>✅ Build your <strong>first custom module</strong> step by step</p>
</li>
</ul>
<hr />
<h2 id="heading-what-are-terraform-modules">📦 What Are Terraform Modules?</h2>
<p>In simple terms, a <strong>module</strong> is just a <strong>collection of Terraform files that work together</strong> to manage a set of related resources.</p>
<p>Every Terraform configuration — even a simple one — is technically a <strong>module</strong> called the <strong>root module</strong>.<br />Any module that you call or include inside it is called a <strong>child module</strong>.</p>
<hr />
<h3 id="heading-why-use-modules">🧩 Why Use Modules?</h3>
<p>Here’s why modules are essential when your infrastructure grows:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Benefit</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Reusability</strong></td><td>Define once, use anywhere — just like a function.</td></tr>
<tr>
<td><strong>Organization</strong></td><td>Group related resources (e.g., VPC, EC2, S3) neatly.</td></tr>
<tr>
<td><strong>Encapsulation</strong></td><td>Hide internal complexity — consumers only see inputs and outputs.</td></tr>
<tr>
<td><strong>Consistency</strong></td><td>Enforce best practices and naming conventions.</td></tr>
<tr>
<td><strong>Collaboration</strong></td><td>Share your modules across your team or projects easily.</td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-think-of-modules-like-functions">💡 Think of Modules Like Functions</h3>
<p>Terraform modules are similar to programming functions:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Programming Concept</td><td>Terraform Equivalent</td></tr>
</thead>
<tbody>
<tr>
<td>Function</td><td>Module</td></tr>
<tr>
<td>Parameters</td><td>Input Variables</td></tr>
<tr>
<td>Return Values</td><td>Output Values</td></tr>
</tbody>
</table>
</div><p>For example, instead of repeating the same EC2 setup in 5 different projects, you can wrap it in a <strong>module</strong> and just call it wherever you need it.</p>
<hr />
<h2 id="heading-module-structure">🏗️ Module Structure</h2>
<p>Let’s look at what a module typically looks like.</p>
<h3 id="heading-basic-module-layout"><strong>Basic Module Layout</strong></h3>
<pre><code class="lang-plaintext">my-module/
├── main.tf          # Main resource definitions
├── variables.tf     # Input variable declarations
├── outputs.tf       # Output value declarations
├── README.md        # Documentation (how to use the module)
└── versions.tf      # Provider and version constraints (optional)
</code></pre>
<p>Each file has a purpose:</p>
<ul>
<li><p><strong>main.tf</strong> → defines your resources (e.g., AWS EC2, S3, etc.)</p>
</li>
<li><p><strong>variables.tf</strong> → defines configurable inputs (like instance type)</p>
</li>
<li><p><strong>outputs.tf</strong> → defines what data you want to expose (like IDs or IPs)</p>
</li>
<li><p><strong>README.md</strong> → explains how to use your module (important when sharing)</p>
</li>
<li><p><strong>versions.tf</strong> → locks provider versions to prevent breaking changes</p>
</li>
</ul>
<hr />
<h3 id="heading-root-module-vs-child-module"><strong>Root Module vs Child Module</strong></h3>
<p>Here’s how your project might look when using multiple modules:</p>
<pre><code class="lang-plaintext">project/
├── main.tf          # Root module (entry point)
├── variables.tf     # Root module inputs
├── outputs.tf       # Root module outputs
└── modules/
    ├── vpc/         # Child module 1
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── ec2/         # Child module 2
        ├── main.tf
        ├── variables.tf
        └── outputs.tf
</code></pre>
<p>The <strong>root module</strong> is your main Terraform directory — the one you run commands in.<br />The <strong>child modules</strong> are reusable building blocks stored in the <code>modules/</code> folder.</p>
<hr />
<h2 id="heading-using-modules-in-terraform">📥 Using Modules in Terraform</h2>
<p>You can include a module using the <code>module</code> block.</p>
<h3 id="heading-basic-module-usage"><strong>Basic Module Usage</strong></h3>
<pre><code class="lang-plaintext">module "module_name" {
  source = "./path/to/module"  # Location of the module folder

  # Provide input variables
  variable_name = value
}
</code></pre>
<hr />
<h3 id="heading-example-creating-and-using-a-simple-s3-module">🧠 Example: Creating and Using a Simple S3 Module</h3>
<p>Let’s create a reusable module that provisions an S3 bucket.</p>
<h4 id="heading-step-1-create-the-module">Step 1: Create the Module</h4>
<p><strong>Folder:</strong> <code>modules/s3-bucket/</code></p>
<p><strong>main.tf</strong></p>
<pre><code class="lang-plaintext">resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name
  tags   = var.tags
}
</code></pre>
<p><strong>variables.tf</strong></p>
<pre><code class="lang-plaintext">variable "bucket_name" {
  description = "Name of the S3 bucket"
  type        = string
}

variable "tags" {
  description = "Tags for the S3 bucket"
  type        = map(string)
  default     = {}
}
</code></pre>
<p><strong>outputs.tf</strong></p>
<pre><code class="lang-plaintext">output "bucket_id" {
  description = "The ID of the S3 bucket"
  value       = aws_s3_bucket.this.id
}

output "bucket_arn" {
  description = "The ARN of the S3 bucket"
  value       = aws_s3_bucket.this.arn
}
</code></pre>
<h4 id="heading-step-2-use-the-module-in-root-configuration">Step 2: Use the Module in Root Configuration</h4>
<p><strong>main.tf</strong></p>
<pre><code class="lang-plaintext">module "logs_bucket" {
  source = "./modules/s3-bucket"

  bucket_name = "my-logs-bucket"
  tags = {
    Purpose = "Logs"
    Owner   = "DevOps"
  }
}

# Access outputs from the module
output "logs_bucket_id" {
  value = module.logs_bucket.bucket_id
}
</code></pre>
<hr />
<h2 id="heading-different-module-sources">🌍 Different Module Sources</h2>
<p>Terraform modules can come from many sources — not just local folders.</p>
<h3 id="heading-1-local-path"><strong>1. Local Path</strong></h3>
<pre><code class="lang-plaintext">module "vpc" {
  source = "./modules/vpc"
}

module "network" {
  source = "../shared-modules/network"
}
</code></pre>
<h3 id="heading-2-terraform-registry"><strong>2. Terraform Registry</strong></h3>
<p>You can use official or community modules from registry.terraform.io.</p>
<pre><code class="lang-plaintext">module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"
}
</code></pre>
<h3 id="heading-3-github"><strong>3. GitHub</strong></h3>
<pre><code class="lang-plaintext">module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc"
}

# Use a specific branch or tag
module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.0.0"
}
</code></pre>
<h3 id="heading-4-s3-bucket"><strong>4. S3 Bucket</strong></h3>
<pre><code class="lang-plaintext">module "vpc" {
  source = "s3::https://s3.amazonaws.com/mybucket/modules/vpc.zip"
}
</code></pre>
<hr />
<h2 id="heading-module-input-variables-passing-data-in">📋 Module Input Variables (Passing Data In)</h2>
<p>Modules use <strong>input variables</strong> to accept configuration from the root module.</p>
<p><strong>Example – modules/ec2/variables.tf</strong></p>
<pre><code class="lang-plaintext">variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "instance_count" {
  description = "Number of instances to launch"
  type        = number
}

variable "tags" {
  description = "Tags for EC2 instances"
  type        = map(string)
  default     = {}
}
</code></pre>
<p><strong>Using the Module:</strong></p>
<pre><code class="lang-plaintext">module "web_servers" {
  source = "./modules/ec2"

  # passing the variable values from the root module main.tf 
  instance_type  = "t2.small"
  instance_count = 3
  tags = {
    Environment = "production"
    Team        = "platform"
    Owner       = "StackOps"
  }
}
</code></pre>
<hr />
<h2 id="heading-module-outputs-getting-data-out-of-a-module">📤 Module Outputs — Getting Data Out of a Module</h2>
<p>In Terraform, <strong>outputs</strong> are a way for a <strong>module to expose information</strong> about the resources it manages.</p>
<p>You can think of outputs like <strong>return values</strong> from a function in programming.</p>
<hr />
<h2 id="heading-what-are-outputs">🧠 What Are Outputs?</h2>
<p>When you define a <strong>module</strong>, it may create multiple resources — for example:</p>
<ul>
<li><p>EC2 instances</p>
</li>
<li><p>S3 buckets</p>
</li>
<li><p>VPCs</p>
</li>
<li><p>Load balancers</p>
</li>
</ul>
<p>Sometimes, you want to use information from those resources <strong>outside</strong> the module (in your root configuration or another module).</p>
<p>That’s where <strong>outputs</strong> come in.</p>
<blockquote>
<p>📦 Outputs let you “export” specific data from a module so that other parts of your Terraform configuration can use it.</p>
</blockquote>
<hr />
<h2 id="heading-output-declaration-the-syntax">⚙️ Output Declaration — The Syntax</h2>
<p>To define an output inside a module, use the <code>output</code> block:</p>
<pre><code class="lang-plaintext">output "&lt;name&gt;" {
  description = "Description of what this output represents"
  value       = &lt;expression&gt;
}
</code></pre>
<p><strong>Fields explained:</strong></p>
<ul>
<li><p><code>name</code> → The name you’ll use to reference this output later (e.g., <code>bucket_arn</code>)</p>
</li>
<li><p><code>description</code> → Optional but helpful when sharing modules</p>
</li>
<li><p><code>value</code> → The actual Terraform expression or attribute you want to expose</p>
</li>
</ul>
<hr />
<h2 id="heading-example-s3-bucket-module-outputs">🧩 Example: S3 Bucket Module Outputs</h2>
<p>Let’s look at a simple example.</p>
<p>Imagine you created an <strong>S3 bucket</strong> module (<code>modules/s3-bucket</code>) like this:</p>
<h3 id="heading-maintfhttpmaintf"><a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-plaintext">resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name
  tags   = var.tags
}
</code></pre>
<p>Now, you might want to expose:</p>
<ul>
<li><p>The <strong>Bucket ID</strong></p>
</li>
<li><p>The <strong>Bucket ARN</strong></p>
</li>
</ul>
<h3 id="heading-outputstfhttpoutputstf"><a target="_blank" href="http://outputs.tf"><strong>outputs.tf</strong></a></h3>
<pre><code class="lang-plaintext">output "bucket_id" {
  description = "The ID of the S3 bucket"
  value       = aws_s3_bucket.this.id
}

output "bucket_arn" {
  description = "The ARN (Amazon Resource Name) of the S3 bucket"
  value       = aws_s3_bucket.this.arn
}
</code></pre>
<hr />
<h2 id="heading-using-outputs-in-the-root-module">🧱 Using Outputs in the Root Module</h2>
<p>Now in your <strong>root module</strong> (the main Terraform project folder), you use the S3 module:</p>
<h3 id="heading-maintfhttpmaintf-1"><a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-plaintext">module "logs_bucket" {
  source = "./modules/s3-bucket"

  bucket_name = "my-logs-bucket"
  tags = {
    Purpose = "Logs"
  }
}
</code></pre>
<p>Once Terraform creates the S3 bucket, the <strong>module outputs</strong> can be accessed using the following syntax:</p>
<pre><code class="lang-plaintext">module.&lt;module_name&gt;.&lt;output_name&gt;
</code></pre>
<p>So in this case:</p>
<pre><code class="lang-plaintext">module.logs_bucket.bucket_id
module.logs_bucket.bucket_arn
</code></pre>
<p>You can use them in:</p>
<ul>
<li><p>Other resource definitions</p>
</li>
<li><p>Other modules</p>
</li>
<li><p>Root-level outputs (to display after apply)</p>
</li>
</ul>
<hr />
<h2 id="heading-displaying-outputs-in-the-root-module">🖥️ Displaying Outputs in the Root Module</h2>
<p>If you want Terraform to <strong>print</strong> those outputs after you run <code>terraform apply</code>, define an <strong>output block</strong> in your root module too:</p>
<pre><code class="lang-plaintext">output "logs_bucket_id" {
  value = module.logs_bucket.bucket_id
}

output "logs_bucket_arn" {
  value = module.logs_bucket.bucket_arn
}
</code></pre>
<p>When you run <code>terraform apply</code>, Terraform will show something like:</p>
<pre><code class="lang-plaintext">logs_bucket_id = "my-logs-bucket"
logs_bucket_arn = "arn:aws:s3:::my-logs-bucket"
</code></pre>
<hr />
<h2 id="heading-real-world-example-using-outputs-between-modules">🔁 Real-World Example — Using Outputs Between Modules</h2>
<p>Let’s say you have:</p>
<ul>
<li><p>A <strong>VPC module</strong> that creates a VPC and subnets.</p>
</li>
<li><p>An <strong>EC2 module</strong> that launches instances.</p>
</li>
</ul>
<p>You want the EC2 module to automatically use the <strong>subnet IDs</strong> from the VPC module.</p>
<hr />
<h3 id="heading-step-1-vpc-module-outputs"><strong>Step 1: VPC Module Outputs</strong></h3>
<p><code>modules/vpc/</code><a target="_blank" href="http://outputs.tf"><code>outputs.tf</code></a></p>
<pre><code class="lang-plaintext">output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnets" {
  value = aws_subnet.public[*].id
}
</code></pre>
<hr />
<h3 id="heading-step-2-ec2-module-inputs"><strong>Step 2: EC2 Module Inputs</strong></h3>
<p><code>modules/ec2/</code><a target="_blank" href="http://variables.tf"><code>variables.tf</code></a></p>
<pre><code class="lang-plaintext">variable "subnet_ids" {
  description = "List of subnet IDs where EC2 instances will be launched"
  type        = list(string)
}
</code></pre>
<hr />
<h3 id="heading-step-3-pass-output-data-between-modules"><strong>Step 3: Pass Output Data Between Modules</strong></h3>
<p>Now in your root module:</p>
<pre><code class="lang-plaintext">module "vpc" {
  source = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
}

module "ec2" {
  source = "./modules/ec2"

  subnet_ids = module.vpc.public_subnets  # 👈 Using output from another module
}
</code></pre>
<p>Terraform will automatically pass the list of subnet IDs from the <strong>VPC module’s outputs</strong> into the <strong>EC2 module’s inputs</strong>.</p>
<hr />
<h2 id="heading-hands-on-lab-build-your-first-module">🧪 Hands-On Lab: Build Your First Module</h2>
<p>Let’s create a reusable VPC module!</p>
<h3 id="heading-step-1-create-project-structure"><strong>Step 1: Create Project Structure</strong></h3>
<pre><code class="lang-bash">mkdir terraform-modules-lab
<span class="hljs-built_in">cd</span> terraform-modules-lab
mkdir -p modules/vpc
</code></pre>
<h3 id="heading-step-2-create-vpc-module"><strong>Step 2: Create VPC Module</strong></h3>
<p><code>modules/vpc/variables.tf</code>:</p>
<pre><code class="lang-plaintext">variable "vpc_name" {
  description = "Name of the VPC"
  type        = string
}

variable "vpc_cidr" {
  description = "CIDR block for VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "azs" {
  description = "Availability zones"
  type        = list(string)
}

variable "public_subnets" {
  description = "Public subnet CIDR blocks"
  type        = list(string)
}

variable "private_subnets" {
  description = "Private subnet CIDR blocks"
  type        = list(string)
  default     = []
}

variable "enable_nat_gateway" {
  description = "Enable NAT gateway"
  type        = bool
  default     = false
}

variable "tags" {
  description = "Additional tags"
  type        = map(string)
  default     = {}
}
</code></pre>
<p><code>modules/vpc/main.tf</code>:</p>
<pre><code class="lang-plaintext"># VPC
resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = merge(
    var.tags,
    { Name = var.vpc_name }
  )
}

# Internet Gateway
resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id

  tags = merge(
    var.tags,
    { Name = "${var.vpc_name}-igw" }
  )
}

# Public Subnets
resource "aws_subnet" "public" {
  count = length(var.public_subnets)

  vpc_id                  = aws_vpc.this.id
  cidr_block              = var.public_subnets[count.index]
  availability_zone       = var.azs[count.index]
  map_public_ip_on_launch = true

  tags = merge(
    var.tags,
    {
      Name = "${var.vpc_name}-public-${count.index + 1}"
      Type = "Public"
    }
  )
}

# Private Subnets
resource "aws_subnet" "private" {
  count = length(var.private_subnets)

  vpc_id            = aws_vpc.this.id
  cidr_block        = var.private_subnets[count.index]
  availability_zone = var.azs[count.index]

  tags = merge(
    var.tags,
    {
      Name = "${var.vpc_name}-private-${count.index + 1}"
      Type = "Private"
    }
  )
}

# Public Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.this.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.this.id
  }

  tags = merge(
    var.tags,
    { Name = "${var.vpc_name}-public-rt" }
  )
}

# Public Route Table Associations
resource "aws_route_table_association" "public" {
  count = length(aws_subnet.public)

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# NAT Gateway (conditional)
resource "aws_eip" "nat" {
  count = var.enable_nat_gateway ? length(var.azs) : 0

  domain = "vpc"

  tags = merge(
    var.tags,
    { Name = "${var.vpc_name}-nat-eip-${count.index + 1}" }
  )
}

resource "aws_nat_gateway" "this" {
  count = var.enable_nat_gateway ? length(var.azs) : 0

  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  tags = merge(
    var.tags,
    { Name = "${var.vpc_name}-nat-${count.index + 1}" }
  )
}

# Private Route Tables (if NAT enabled)
resource "aws_route_table" "private" {
  count = var.enable_nat_gateway ? length(var.azs) : 0

  vpc_id = aws_vpc.this.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.this[count.index].id
  }

  tags = merge(
    var.tags,
    { Name = "${var.vpc_name}-private-rt-${count.index + 1}" }
  )
}

# Private Route Table Associations
resource "aws_route_table_association" "private" {
  count = var.enable_nat_gateway ? length(aws_subnet.private) : 0

  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private[count.index].id
}
</code></pre>
<p><code>modules/vpc/outputs.tf</code>:</p>
<pre><code class="lang-plaintext">output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.this.id
}

output "vpc_cidr" {
  description = "VPC CIDR block"
  value       = aws_vpc.this.cidr_block
}

output "public_subnet_ids" {
  description = "Public subnet IDs"
  value       = aws_subnet.public[*].id
}

output "private_subnet_ids" {
  description = "Private subnet IDs"
  value       = aws_subnet.private[*].id
}

output "igw_id" {
  description = "Internet Gateway ID"
  value       = aws_internet_gateway.this.id
}

output "nat_gateway_ids" {
  description = "NAT Gateway IDs"
  value       = aws_nat_gateway.this[*].id
}

output "public_route_table_id" {
  description = "Public route table ID"
  value       = aws_route_table.public.id
}

output "private_route_table_ids" {
  description = "Private route table IDs"
  value       = aws_route_table.private[*].id
}
</code></pre>
<h3 id="heading-step-3-use-the-module"><strong>Step 3: Use the Module</strong></h3>
<p>Create root module files:</p>
<p><code>main.tf</code>:</p>
<pre><code class="lang-plaintext">data "aws_availability_zones" "available" {
  state = "available"
}

# Development VPC
module "dev_vpc" {
  source = "./modules/vpc"

  vpc_name        = "dev-vpc"
  vpc_cidr        = "10.0.0.0/16"
  azs             = slice(data.aws_availability_zones.available.names, 0, 2)
  public_subnets  = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnets = ["10.0.11.0/24", "10.0.12.0/24"]

  enable_nat_gateway = false

  tags = {
    Environment = "development"
    Project     = "modules-lab"
    Owner       = "StackOps"
  }
}

# Production VPC
module "prod_vpc" {
  source = "./modules/vpc"

  vpc_name        = "prod-vpc"
  vpc_cidr        = "172.16.0.0/16"
  azs             = slice(data.aws_availability_zones.available.names, 0, 3)
  public_subnets  = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"]
  private_subnets = ["172.16.11.0/24", "172.16.12.0/24", "172.16.13.0/24"]

  enable_nat_gateway = true

  tags = {
    Environment = "production"
    Project     = "modules-lab"
    Owner       = "StackOps"
  }
}
</code></pre>
<p><code>variables.tf</code>:</p>
<pre><code class="lang-plaintext">variable "aws_region" {
  type    = string
  default = "us-east-1"
}
</code></pre>
<p><code>outputs.tf</code>:</p>
<pre><code class="lang-plaintext">output "dev_vpc_info" {
  description = "Development VPC information"
  value = {
    vpc_id             = module.dev_vpc.vpc_id
    public_subnet_ids  = module.dev_vpc.public_subnet_ids
    private_subnet_ids = module.dev_vpc.private_subnet_ids
  }
}

output "prod_vpc_info" {
  description = "Production VPC information"
  value = {
    vpc_id             = module.prod_vpc.vpc_id
    public_subnet_ids  = module.prod_vpc.public_subnet_ids
    private_subnet_ids = module.prod_vpc.private_subnet_ids
    nat_gateway_ids    = module.prod_vpc.nat_gateway_ids
  }
}
</code></pre>
<p><code>providers.tf</code>:</p>
<pre><code class="lang-plaintext">terraform {
  required_version = "&gt;= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}
</code></pre>
<h3 id="heading-step-4-deploy"><strong>Step 4: Deploy</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize (downloads module dependencies)</span>
terraform fmt
terraform init
<span class="hljs-comment"># Plan</span>
terraform plan
<span class="hljs-comment"># Apply</span>
terraform apply
<span class="hljs-comment"># View outputs</span>
terraform output
<span class="hljs-comment"># Destroy</span>
terraform destroy -auto-approve
</code></pre>
<h2 id="heading-module-best-practices">🔗 Module Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Keep modules focused</strong> - One purpose per module</p>
</li>
<li><p><strong>Use clear naming</strong> - Descriptive variable names</p>
</li>
<li><p><strong>Document everything</strong> - README, variable descriptions</p>
</li>
<li><p><strong>Version your modules</strong> - Use tags/releases</p>
</li>
<li><p><strong>Provide defaults</strong> - Sensible default values</p>
</li>
<li><p><strong>Use outputs</strong> - Expose useful information</p>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p><strong>Don’t hardcode values</strong> - Use variables</p>
</li>
<li><p><strong>Don’t create god modules</strong> - Keep them small</p>
</li>
<li><p><strong>Don’t skip documentation</strong></p>
</li>
<li><p><strong>Don’t ignore versioning</strong></p>
</li>
</ol>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ What modules are and their benefits</p>
</li>
<li><p>✅ Module structure and organization</p>
</li>
<li><p>✅ Module sources (local, registry, git)</p>
</li>
<li><p>✅ Input variables and outputs</p>
</li>
<li><p>✅ Creating and using custom modules</p>
</li>
<li><p>✅ Module best practices</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 18: Creating Your Own Reusable Modules</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Design production-ready modules</p>
</li>
<li><p>Handle optional resources</p>
</li>
<li><p>Create module composition patterns</p>
</li>
<li><p>Implement module testing</p>
</li>
<li><p>Publish modules to registry</p>
</li>
</ul>
<hr />
<p><strong>Happy Learning! 🎉</strong></p>
<blockquote>
<p><strong><em>Thanks For Reading, Follow Me For More</em></strong></p>
<p><strong><em>Subscribe</em></strong> <a target="_blank" href="https://www.youtube.com/channel/UCWQ_nnU_fdvqfMyyn-RCu_A"><strong><em>youtube</em></strong></a> <strong><em>channel for the recap video</em></strong></p>
<p><strong><em>Have a great day!..</em></strong></p>
</blockquote>
<p><a target="_blank" href="https://stackopsdiary.site/day-16-dynamic-blocks-for-flexible-resources">← Day 1</a><a target="_blank" href="https://www.youtube.com/channel/UCWQ_nnU_fdvqfMyyn-RCu_A">6: Dynamic</a> <a target="_blank" href="https://stackopsdiary.site/day-16-dynamic-blocks-for-flexible-resources">Blocks</a> | <a target="_blank" href="day18.md">D</a><a target="_blank" href="https://www.youtube.com/channel/UCWQ_nnU_fdvqfMyyn-RCu_A">ay 18:</a> <a target="_blank" href="day18.md">Custom Modules →</a></p>
<hr />
<p><strong><em>Remember*</em></strong>: Modules are the<em> <a target="_blank" href="https://www.youtube.com/channel/UCWQ_nnU_fdvqfMyyn-RCu_A"></a></em>foundation of scalable, maintainable infrastructure!*</p>
]]></content:encoded></item><item><title><![CDATA[Day 16: Dynamic Blocks for Flexible Resources]]></title><description><![CDATA[Welcome to Day 16! Today we’ll master dynamic blocks - a powerful feature that allows you to dynamically generate nested configuration blocks. This makes your resources incredibly flexible and reduces repetition.
🎯 Today’s Goals

Understand dynamic ...]]></description><link>https://stackopsdiary.site/day-16-dynamic-blocks-for-flexible-resources</link><guid isPermaLink="true">https://stackopsdiary.site/day-16-dynamic-blocks-for-flexible-resources</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Thu, 23 Oct 2025 12:30:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761206209441/65b79077-c2c5-4942-9ae0-27e11a56d4d6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 16! Today we’ll master <strong>dynamic blocks</strong> - a powerful feature that allows you to dynamically generate nested configuration blocks. This makes your resources incredibly flexible and reduces repetition.</p>
<h3 id="heading-todays-goals">🎯 Today’s Goals</h3>
<ul>
<li><p>Understand dynamic blocks and their purpose</p>
</li>
<li><p>Create dynamic security group rules</p>
</li>
<li><p>Use for_each in dynamic blocks</p>
</li>
<li><p>Handle complex nested blocks</p>
</li>
<li><p>Build flexible, data-driven resources</p>
</li>
</ul>
<h3 id="heading-what-is-a-dynamic-block-in-terraform">🧩 What is a Dynamic Block in Terraform?</h3>
<p>A <strong>dynamic block</strong> allows you to <strong>generate nested blocks dynamically</strong> inside a resource — especially useful when the <strong>number of nested blocks changes based on variables or lists</strong>.</p>
<p>Normally, in Terraform, you would define multiple <strong>ingress</strong> or <strong>egress</strong> rules like this:</p>
<pre><code class="lang-plaintext">resource "aws_security_group" "example" {
  name        = "example-sg"
  description = "Example security group"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
</code></pre>
<p>But what if your ingress rules are <strong>dynamic</strong>, and you want to <strong>loop</strong> over them instead of writing each one manually?</p>
<p>That’s where <code>dynamic</code> blocks come in.</p>
<hr />
<h3 id="heading-syntax-of-a-dynamic-block">🚀 Syntax of a Dynamic Block</h3>
<p>A <code>dynamic</code> block has this format:</p>
<pre><code class="lang-plaintext">dynamic "&lt;block_label&gt;" {
  for_each = &lt;collection&gt;
  content {
    # configuration for each generated block
  }
}
</code></pre>
<h3 id="heading-explanation">Explanation:</h3>
<ul>
<li><p><code>block_label</code> → the nested block name (e.g., <code>ingress</code>, <code>egress</code>)</p>
</li>
<li><p><code>for_each</code> → the list or map to iterate over</p>
</li>
<li><p><code>content</code> → defines the inside of each block</p>
</li>
</ul>
<hr />
<h3 id="heading-example-aws-security-group-with-dynamic-ingress-rules">🛠 Example: AWS Security Group with Dynamic Ingress Rules</h3>
<p>Let’s make it practical. 👇</p>
<h3 id="heading-variablestfhttpvariablestf"><a target="_blank" href="http://variables.tf"><code>variables.tf</code></a></h3>
<pre><code class="lang-plaintext">variable "ingress_rules" {
  type = list(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
  }))
  default = [
    {
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  ]
}
</code></pre>
<h3 id="heading-maintfhttpmaintf"><a target="_blank" href="http://main.tf"><code>main.tf</code></a></h3>
<pre><code class="lang-plaintext">provider "aws" {
  region = "ap-southeast-1"
}

resource "aws_security_group" "web_sg" {
  name        = "web-sg"
  description = "Security group with dynamic ingress rules"
  vpc_id      = "vpc-1234567890abcdef0" # Replace with your VPC ID

  # Dynamic ingress rule generation
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "web-sg"
  }
}
</code></pre>
<hr />
<h3 id="heading-whats-happening-here">🧠 What’s Happening Here?</h3>
<p>Terraform will <strong>loop through each item</strong> in <code>var.ingress_rules</code> and create one <code>ingress</code> block for each.</p>
<p>So the final plan will be equivalent to manually writing:</p>
<pre><code class="lang-plaintext">ingress {
  from_port   = 22
  to_port     = 22
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
}

ingress {
  from_port   = 80
  to_port     = 80
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
}

ingress {
  from_port   = 443
  to_port     = 443
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
}
</code></pre>
<hr />
<h2 id="heading-optional-using-maps-instead-of-lists">🧩 Optional — Using Maps Instead of Lists</h2>
<p>You can also use a map, which gives you access to both the <strong>key</strong> and <strong>value</strong>.</p>
<pre><code class="lang-plaintext">provider "aws" {
  region = "ap-southeast-1"
}

variable "ingress_rules_map" {
  type = map(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
  }))

  default = {
    ssh = {
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
    http = {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

resource "aws_security_group" "web_sg_map" {
  name   = "web-sg-map"
  vpc_id = "vpc-1234567890abcdef0"

  dynamic "ingress" {
    for_each = var.ingress_rules_map
    content {
      description = ingress.key
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}
</code></pre>
<p>This version adds a <strong>description</strong> for each ingress rule based on the map key (e.g., <code>"ssh"</code>, <code>"http"</code>).</p>
<hr />
<h2 id="heading-benefits-of-using-dynamic-blocks">✅ Benefits of Using Dynamic Blocks</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Benefit</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>💡 Reusable</td><td>You can reuse the same code for many environments (dev, staging, prod)</td></tr>
<tr>
<td>🧱 Clean</td><td>Avoid repeating similar nested blocks</td></tr>
<tr>
<td>🧮 Data-driven</td><td>Easily control rules from variables or JSON files</td></tr>
<tr>
<td>⚙️ Flexible</td><td>Works great with <code>for_each</code>, <code>locals</code>, and even <code>count</code></td></tr>
</tbody>
</table>
</div><hr />
<p>Let’s go deep into <strong>nested dynamic blocks</strong> — like how to dynamically generate <strong>tags</strong>, or even deeper structures inside a resource.</p>
<hr />
<h3 id="heading-what-is-a-nested-dynamic-block">🧩 What Is a Nested Dynamic Block?</h3>
<p>A <strong>nested dynamic block</strong> means a <code>dynamic</code> block <strong>inside another</strong> <code>dynamic</code> block or a nested configuration.</p>
<p>In simple words:</p>
<blockquote>
<p>You use a dynamic block inside another block (static or dynamic) to generate multiple <strong>levels</strong> of configuration dynamically.</p>
</blockquote>
<hr />
<h3 id="heading-why-do-we-need-nested-dynamic-blocks">🧠 Why Do We Need Nested Dynamic Blocks?</h3>
<p>Some Terraform resources (like <code>aws_lb_listener</code>, <code>aws_ecs_task_definition</code>, etc.) have <strong>deeply nested configurations</strong> — e.g.:</p>
<ul>
<li><p>multiple rules</p>
</li>
<li><p>each rule has multiple <code>cidr_blocks</code> or <code>tags</code></p>
</li>
<li><p>each tag or rule may differ depending on input</p>
</li>
</ul>
<p>In those cases, you can <strong>nest</strong> <code>dynamic</code> blocks.</p>
<hr />
<h3 id="heading-example-1-dynamic-nested-dynamic-block-for-alb-listener">⚙️ Example 1: Dynamic + Nested Dynamic Block for ALB listener</h3>
<p><strong>Goal:</strong> Create an AWS Application Load Balancer (ALB) listener</p>
<ul>
<li><p>with multiple <strong>actions</strong> (dynamic)</p>
</li>
<li><p>each action can have <strong>multiple target groups</strong> (nested dynamic).</p>
</li>
</ul>
<p>You’ll see a real, supported nested <code>dynamic</code> → <code>dynamic</code> pattern.</p>
<h3 id="heading-variablestfhttpvariablestf-1">🗂️ <a target="_blank" href="http://variables.tf"><code>variables.tf</code></a></h3>
<pre><code class="lang-plaintext">variable "listener_actions" {
  description = "List of listener actions with nested target groups"
  type = list(object({
    type          = string
    order         = number
    target_groups = list(object({
      arn   = string
      weight = number
    }))
  }))

  # Example demo data
  default = [
    {
      type  = "forward"
      order = 1
      target_groups = [
        { arn = "arn:aws:elasticloadbalancing:ap-southeast-1:111122223333:targetgroup/app1-tg/aaaa", weight = 1 },
        { arn = "arn:aws:elasticloadbalancing:ap-southeast-1:111122223333:targetgroup/app2-tg/bbbb", weight = 2 }
      ]
    },
    {
      type  = "redirect"
      order = 2
      target_groups = []
    }
  ]
}
</code></pre>
<h3 id="heading-maintfhttpmaintf-1">🗂️ <a target="_blank" href="http://main.tf"><code>main.tf</code></a></h3>
<pre><code class="lang-plaintext">provider "aws" {
  region = "ap-southeast-1"
}

# Mock Load Balancer just for structure demo (you can skip or replace)
resource "aws_lb" "demo_lb" {
  name               = "demo-alb"
  internal           = false
  load_balancer_type = "application"
  subnets            = ["subnet-aaa111", "subnet-bbb222"] # replace with yours
}

resource "aws_lb_listener" "demo_listener" {
  load_balancer_arn = aws_lb.demo_lb.arn
  port              = 80
  protocol          = "HTTP"

  # ----------------------------------- #
  # Dynamic Block: default_action       #
  # ----------------------------------- #
  dynamic "default_action" {
    for_each = var.listener_actions
    content {
      type  = default_action.value.type
      order = default_action.value.order

      # ----------------------------------- #
      # Nested Dynamic Block: forward block #
      # ----------------------------------- #
      dynamic "forward" {
        for_each = (
          default_action.value.type == "forward" ?
          [default_action.value] : []
        )
        content {

          # ------------------------------------------------- #
          # Nested Dynamic Block inside forward: target_group #
          # ------------------------------------------------- #
          dynamic "target_group" {
            for_each = forward.value.target_groups
            content {
              arn    = target_group.value.arn
              weight = target_group.value.weight
            }
          }
        }
      }

      # Example: redirect block for second action
      dynamic "redirect" {
        for_each = (
          default_action.value.type == "redirect" ?
          [default_action.value] : []
        )
        content {
          port        = "443"
          protocol    = "HTTPS"
          status_code = "HTTP_301"
        }
      }
    }
  }
}
</code></pre>
<h3 id="heading-outputstfhttpmaintf">🗂️ <a target="_blank" href="http://main.tf"><code>outputs.tf</code></a></h3>
<pre><code class="lang-plaintext">output "listener_actions_result" {
  value = var.listener_actions
}
</code></pre>
<h3 id="heading-run-amp-test">Run &amp; Test</h3>
<pre><code class="lang-plaintext">terraform init
terraform validate
terraform plan
</code></pre>
<p>✅ Expected result:<br />Terraform will <strong>successfully validate</strong> and show dynamically generated nested blocks:</p>
<pre><code class="lang-plaintext">Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + listener_actions_result = [
      + {
          + order         = 1
          + target_groups = [
              + {
                  + arn    = "arn:aws:elasticloadbalancing:ap-southeast-1:111122223333:targetgroup/app1-tg/aaaa"
                  + weight = 1
                },
              + {
                  + arn    = "arn:aws:elasticloadbalancing:ap-southeast-1:111122223333:targetgroup/app2-tg/bbbb"
                  + weight = 2
                },
            ]
          + type          = "forward"
        },
      + {
          + order         = 2
          + target_groups = []
          + type          = "redirect"
        },
    ]
</code></pre>
<hr />
<h2 id="heading-example-2-simple-dynamic-tags-for-any-resource">🏗 Example 2: Simple Dynamic Tags for Any Resource</h2>
<p>If your resource supports a <code>tags</code> block (not just a map), you can use:</p>
<pre><code class="lang-plaintext">dynamic "tags" {
  for_each = var.tags
  content {
    key   = tags.key
    value = tags.value
  }
}
</code></pre>
<p>But—most AWS resources already accept <code>tags = {}</code> maps directly, so you usually don’t <em>need</em> a dynamic block for tags.</p>
<hr />
<h2 id="heading-key-takeaways">✅ Key Takeaways</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Concept</td><td>Meaning</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Dynamic block</strong></td><td>Used to generate one or more nested blocks dynamically</td></tr>
<tr>
<td><strong>Nested dynamic block</strong></td><td>A dynamic block <strong>inside another dynamic</strong> or block</td></tr>
<tr>
<td><strong>When to use</strong></td><td>When you have nested repeating structures (like network interfaces, rules, tags)</td></tr>
<tr>
<td><strong>Not for</strong></td><td>Simple attributes (like <code>cidr_blocks = []</code>) — only for real sub-blocks</td></tr>
<tr>
<td><strong>Common usage</strong></td><td>AWS <code>launch_template</code>, <code>listener_rule</code>, <code>security_group_rule</code>, etc.</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-hands-on-lab-dynamic-blocks-mastery">🧪 Hands-On Lab: Dynamic Blocks Mastery</h2>
<p>Let’s build flexible infrastructure using dynamic blocks!</p>
<h3 id="heading-step-1-create-project"><strong>Step 1: Create Project</strong></h3>
<pre><code class="lang-bash">mkdir terraform-dynamic-blocks-lab
<span class="hljs-built_in">cd</span> terraform-dynamic-blocks-lab
</code></pre>
<h3 id="heading-step-2-create-variablestfhttpvariablestf"><strong>Step 2: Create</strong> <a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a></h3>
<pre><code class="lang-plaintext"># variables.tf

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "project_name" {
  type    = string
  default = "dynamic-demo"
}

variable "environment" {
  type    = string
  default = "dev"
}

variable "security_groups" {
  description = "Security group configurations"
  type = map(object({
    description = string
    ingress_rules = list(object({
      description = string
      from_port   = number
      to_port     = number
      protocol    = string
      cidr_blocks = list(string)
    }))
    egress_rules = list(object({
      description = string
      from_port   = number
      to_port     = number
      protocol    = string
      cidr_blocks = list(string)
    }))
  }))

  default = {
    web = {
      description = "Security group for web servers"
      ingress_rules = [
        {
          description = "HTTP from anywhere"
          from_port   = 80
          to_port     = 80
          protocol    = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
        },
        {
          description = "HTTPS from anywhere"
          from_port   = 443
          to_port     = 443
          protocol    = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
        }
      ]
      egress_rules = [
        {
          description = "All traffic"
          from_port   = 0
          to_port     = 0
          protocol    = "-1"
          cidr_blocks = ["0.0.0.0/0"]
        }
      ]
    }

    app = {
      description = "Security group for app servers"
      ingress_rules = [
        {
          description = "App port from VPC"
          from_port   = 8080
          to_port     = 8080
          protocol    = "tcp"
          cidr_blocks = ["10.0.0.0/16"]
        }
      ]
      egress_rules = [
        {
          description = "HTTPS to internet"
          from_port   = 443
          to_port     = 443
          protocol    = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
        },
        {
          description = "Database access"
          from_port   = 5432
          to_port     = 5432
          protocol    = "tcp"
          cidr_blocks = ["10.0.0.0/16"]
        }
      ]
    }

    db = {
      description = "Security group for databases"
      ingress_rules = [
        {
          description = "PostgreSQL from app tier"
          from_port   = 5432
          to_port     = 5432
          protocol    = "tcp"
          cidr_blocks = ["10.0.0.0/16"]
        }
      ]
      egress_rules = []
    }
  }
}

variable "instances" {
  description = "Instance configurations with dynamic volumes"
  type = map(object({
    instance_type = string
    ami_filter    = string
    volumes = list(object({
      device_name = string
      size        = number
      type        = string
    }))
    tags = map(string)
  }))

  default = {
    web = {
      instance_type = "t2.micro"
      ami_filter    = "amzn2-ami-hvm-*"
      volumes = [
        { device_name = "/dev/sdf", size = 30, type = "gp3" }
      ]
      tags = {
        Tier = "Web"
        Role = "Frontend"
      }
    }

    app = {
      instance_type = "t2.small"
      ami_filter    = "amzn2-ami-hvm-*"
      volumes = [
        { device_name = "/dev/sdf", size = 50, type = "gp3" },
        { device_name = "/dev/sdg", size = 100, type = "gp3" }
      ]
      tags = {
        Tier = "App"
        Role = "Backend"
      }
    }
  }
}

variable "s3_buckets" {
  description = "S3 bucket configurations with dynamic lifecycle rules"
  type = map(object({
    versioning = bool
    lifecycle_rules = list(object({
      id         = string
      enabled    = bool
      prefix     = string
      expiration_days = number
    }))
  }))

  default = {
    logs = {
      versioning = true
      lifecycle_rules = [
        {
          id              = "delete-old-logs"
          enabled         = true
          prefix          = "logs/"
          expiration_days = 90
        },
        {
          id              = "delete-temp"
          enabled         = true
          prefix          = "temp/"
          expiration_days = 7
        }
      ]
    }

    data = {
      versioning = true
      lifecycle_rules = [
        {
          id              = "archive-old-data"
          enabled         = true
          prefix          = "archive/"
          expiration_days = 365
        }
      ]
    }
  }
}
</code></pre>
<h3 id="heading-step-3-create-maintfhttpmaintf"><strong>Step 3: Create</strong> <a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-plaintext"># main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~&gt; 3.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# VPC
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

# Subnets
resource "aws_subnet" "public" {
  count = 2

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "${var.project_name}-public-${count.index + 1}"
  }
}

data "aws_availability_zones" "available" {
  state = "available"
}

# Security Groups with Dynamic Blocks
resource "aws_security_group" "groups" {
  for_each = var.security_groups

  name        = "${var.project_name}-${each.key}-sg"
  description = each.value.description
  vpc_id      = aws_vpc.main.id

  # Dynamic ingress rules
  dynamic "ingress" {
    for_each = each.value.ingress_rules
    iterator = rule

    content {
      description = rule.value.description
      from_port   = rule.value.from_port
      to_port     = rule.value.to_port
      protocol    = rule.value.protocol
      cidr_blocks = rule.value.cidr_blocks
    }
  }

  # Dynamic egress rules
  dynamic "egress" {
    for_each = each.value.egress_rules
    iterator = rule

    content {
      description = rule.value.description
      from_port   = rule.value.from_port
      to_port     = rule.value.to_port
      protocol    = rule.value.protocol
      cidr_blocks = rule.value.cidr_blocks
    }
  }

  tags = {
    Name = "${var.project_name}-${each.key}-sg"
    Tier = title(each.key)
  }
}

# AMI Data Source
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# EC2 Instances with Dynamic EBS Volumes
resource "aws_instance" "servers" {
  for_each = var.instances

  ami           = data.aws_ami.amazon_linux.id
  instance_type = each.value.instance_type
  subnet_id     = aws_subnet.public[0].id

  vpc_security_group_ids = [
    aws_security_group.groups[each.key].id
  ]

  # Dynamic EBS volumes
  dynamic "ebs_block_device" {
    for_each = each.value.volumes
    iterator = volume

    content {
      device_name = volume.value.device_name
      volume_size = volume.value.size
      volume_type = volume.value.type
      encrypted   = true
      tags = {
        Name = "${var.project_name}-${each.key}-volume-${volume.key}"
      }
    }
  }

  tags = merge(
    each.value.tags,
    {
      Name        = "${var.project_name}-${each.key}"
      Environment = var.environment
    }
  )
}

# S3 Buckets with Dynamic Lifecycle Rules
resource "random_id" "bucket" {
  for_each    = var.s3_buckets
  byte_length = 4
}

resource "aws_s3_bucket" "buckets" {
  for_each = var.s3_buckets

  bucket = "${var.project_name}-${each.key}-${random_id.bucket[each.key].hex}"

  tags = {
    Name = "${var.project_name}-${each.key}"
    Type = each.key
  }
}

resource "aws_s3_bucket_versioning" "buckets" {
  for_each = var.s3_buckets

  bucket = aws_s3_bucket.buckets[each.key].id

  versioning_configuration {
    status = each.value.versioning ? "Enabled" : "Disabled"
  }
}

resource "aws_s3_bucket_lifecycle_configuration" "buckets" {
  for_each = var.s3_buckets

  bucket = aws_s3_bucket.buckets[each.key].id

  dynamic "rule" {
    for_each = each.value.lifecycle_rules
    iterator = lifecycle

    content {
      id     = lifecycle.value.id
      status = lifecycle.value.enabled ? "Enabled" : "Disabled"

      filter {
        prefix = lifecycle.value.prefix
      }

      expiration {
        days = lifecycle.value.expiration_days
      }
    }
  }
}
</code></pre>
<h3 id="heading-step-4-create-outputstfhttpoutputstf"><strong>Step 4: Create</strong> <a target="_blank" href="http://outputs.tf"><strong>outputs.tf</strong></a></h3>
<pre><code class="lang-plaintext"># outputs.tf

output "security_groups" {
  description = "Security group details"
  value = {
    for key, sg in aws_security_group.groups :
    key =&gt; {
      id               = sg.id
      name             = sg.name
      ingress_count    = length(var.security_groups[key].ingress_rules)
      egress_count     = length(var.security_groups[key].egress_rules)
    }
  }
}

output "instances" {
  description = "Instance details"
  value = {
    for key, instance in aws_instance.servers :
    key =&gt; {
      id          = instance.id
      private_ip  = instance.private_ip
      volume_count = length(var.instances[key].volumes)
      tags        = var.instances[key].tags
    }
  }
}

output "s3_buckets" {
  description = "S3 bucket details"
  value = {
    for key, bucket in aws_s3_bucket.buckets :
    key =&gt; {
      name                = bucket.id
      versioning          = var.s3_buckets[key].versioning
      lifecycle_rule_count = length(var.s3_buckets[key].lifecycle_rules)
    }
  }
}

output "dynamic_block_summary" {
  description = "Summary of resources created with dynamic blocks"
  value = {
    security_groups = {
      total           = length(aws_security_group.groups)
      total_ingress   = sum([for sg in var.security_groups : length(sg.ingress_rules)])
      total_egress    = sum([for sg in var.security_groups : length(sg.egress_rules)])
    }
    instances = {
      total         = length(aws_instance.servers)
      total_volumes = sum([for inst in var.instances : length(inst.volumes)])
    }
    s3_buckets = {
      total                 = length(aws_s3_bucket.buckets)
      total_lifecycle_rules = sum([for bucket in var.s3_buckets : length(bucket.lifecycle_rules)])
    }
  }
}
</code></pre>
<h3 id="heading-step-5-deploy-and-test"><strong>Step 5: Deploy and Test</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init
<span class="hljs-comment"># Plan</span>
terraform plan
<span class="hljs-comment"># Apply</span>
terraform apply -auto-approve
<span class="hljs-comment"># View outputs</span>
terraform output
<span class="hljs-comment"># Check security group rules</span>
terraform output security_groups
<span class="hljs-comment"># Check instance volumes</span>
terraform output instances
<span class="hljs-comment"># Check S3 lifecycle rules</span>
terraform output s3_buckets
<span class="hljs-comment"># Clean up</span>
terraform destroy -auto-approve
</code></pre>
<h2 id="heading-best-practices">📝 Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Use dynamic blocks for repeated nested blocks</strong></p>
</li>
<li><p><strong>Keep dynamic blocks simple and readable</strong></p>
</li>
<li><p><strong>Document complex dynamic structures</strong></p>
</li>
<li><p><strong>Validate input data structure</strong></p>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p><strong>Don’t use dynamic blocks for single blocks</strong></p>
</li>
<li><p><strong>Don’t nest too many levels of dynamic blocks</strong></p>
</li>
<li><p><strong>Don’t create overly complex conditions in dynamic blocks</strong></p>
</li>
<li><p><strong>Don’t use dynamic blocks when static blocks are clearer</strong></p>
</li>
</ol>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Dynamic block syntax and structure</p>
</li>
<li><p>✅ Creating dynamic security group rules</p>
</li>
<li><p>✅ Nested dynamic blocks</p>
</li>
<li><p>✅ Dynamic blocks with for_each</p>
</li>
<li><p>✅ Real-world dynamic block patterns</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 17: Introduction to Modules</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Understand Terraform modules</p>
</li>
<li><p>Learn module structure</p>
</li>
<li><p>Use public modules</p>
</li>
<li><p>Pass data between modules</p>
</li>
<li><p>Organize code with modules</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-15-built-in-functions-in-terraform-part-ii">← Day 15: Built-in Functions</a> | <a target="_blank" href="day17.md">Day 17: Introduction to Modules →</a></p>
<hr />
<p><em>Remember: Dynamic blocks eliminate repetition and make resources data-driven!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 15: Built-in Functions in Terraform Part-II]]></title><description><![CDATA[Today, we’ll explore Terraform functions — powerful helpers that make your infrastructure more flexible and smart.
Terraform functions allow you to manipulate data, perform calculations, handle text, work with networks, and much more — all inside you...]]></description><link>https://stackopsdiary.site/day-15-built-in-functions-in-terraform-part-ii</link><guid isPermaLink="true">https://stackopsdiary.site/day-15-built-in-functions-in-terraform-part-ii</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Wed, 22 Oct 2025 12:30:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761126269521/55518780-7625-4e12-9969-d955e7542f56.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today, we’ll explore <strong>Terraform functions</strong> — powerful helpers that make your infrastructure more flexible and smart.</p>
<p>Terraform functions allow you to <strong>manipulate data, perform calculations, handle text, work with networks</strong>, and much more — all inside your configuration files.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<p>By the end of this lesson, you’ll be able to:</p>
<ul>
<li><p>Master essential Terraform functions</p>
</li>
<li><p>Use encoding and cryptographic functions</p>
</li>
<li><p>Work with date/time and filesystem functions</p>
</li>
<li><p>Apply functions to real-world scenarios</p>
</li>
</ul>
<hr />
<h3 id="heading-encoding-functions">🔐 <strong>Encoding Functions</strong></h3>
<p>Terraform’s encoding functions convert data between different formats so you can use it across APIs, files, and configurations.<br />These are extremely useful when you:</p>
<ul>
<li><p>pass data to AWS user_data or templates</p>
</li>
<li><p>store structured info in strings</p>
</li>
<li><p>interact with APIs that expect Base64, JSON, or YAML</p>
</li>
</ul>
<hr />
<h3 id="heading-1-base64encode-and-base64decode">🧩 1. <code>base64encode()</code> and <code>base64decode()</code></h3>
<p><strong>📘 Description</strong></p>
<p>These convert text into Base64 format and back again.</p>
<ul>
<li><p><code>base64encode(string)</code> → encodes plain text</p>
</li>
<li><p><code>base64decode(base64_string)</code> → decodes back to original text</p>
</li>
</ul>
<p>Base64 encoding is commonly used in:</p>
<ul>
<li><p><strong>AWS EC2 user_data scripts</strong></p>
</li>
<li><p><strong>Secrets</strong> stored in S3 or external systems</p>
</li>
<li><p><strong>API payloads</strong></p>
</li>
</ul>
<h3 id="heading-example">🧠 Example</h3>
<pre><code class="lang-plaintext">locals {
  original = "Hello, World"
  encoded  = base64encode(local.original)
  decoded  = base64decode(local.encoded)
}
</code></pre>
<h3 id="heading-output">🧾 Output</h3>
<pre><code class="lang-plaintext">encoded = "SGVsbG8sIFdvcmxkCg=="
decoded = "Hello, World"
</code></pre>
<h3 id="heading-real-world-use-case">💡 Real-world use case</h3>
<p>Encoding a startup script for an EC2 instance:</p>
<pre><code class="lang-plaintext">resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  user_data     = base64encode(file("${path.module}/scripts/startup.sh"))
}
</code></pre>
<p>→ AWS expects Base64 input, so this function is perfect.</p>
<hr />
<h3 id="heading-2-jsonencode-and-jsondecode">💾 2. <code>jsonencode()</code> and <code>jsondecode()</code></h3>
<p><strong>📘 Description</strong></p>
<p>Terraform natively uses HCL (HashiCorp Configuration Language),<br />but many cloud APIs and tools (like AWS Lambda, Datadog, Kubernetes) require <strong>JSON</strong> input.</p>
<ul>
<li><p><code>jsonencode(map or list)</code> → converts Terraform data into JSON string</p>
</li>
<li><p><code>jsondecode(json_string)</code> → parses JSON back into Terraform data types</p>
</li>
</ul>
<h3 id="heading-example-1">🧠 Example</h3>
<pre><code class="lang-plaintext">locals {
  data = {
    name    = "Terraform"
    version = 1.6
  }

  json_string = jsonencode(local.data)
  parsed      = jsondecode(local.json_string)
}
</code></pre>
<h3 id="heading-output-1">🧾 Output</h3>
<pre><code class="lang-plaintext">json_string = "{\"name\":\"Terraform\",\"version\":1.6}"
parsed      = { name = "Terraform", version = 1.6 }
</code></pre>
<h3 id="heading-real-world-use-case-1">💡 Real-world use case</h3>
<p>You can store configuration data in S3 or pass it to an API in JSON format:</p>
<pre><code class="lang-plaintext">resource "aws_s3_object" "config" {
  bucket = "my-config-bucket"
  key    = "app_config.json"
  content = jsonencode({
    env     = "production"
    version = "1.0.0"
    enabled = true
  })
}
</code></pre>
<p>This automatically converts the Terraform map into a JSON file inside S3.</p>
<hr />
<h3 id="heading-3-yamlencode-and-yamldecode">🧱 3. <code>yamlencode()</code> and <code>yamldecode()</code></h3>
<p><strong>📘 Description</strong></p>
<p>YAML is widely used for configuration files (like Kubernetes manifests or Ansible playbooks).<br />Terraform can convert its internal data structures to YAML and back.</p>
<ul>
<li><p><code>yamlencode()</code> → Converts Terraform data → YAML string</p>
</li>
<li><p><code>yamldecode()</code> → Parses YAML string → Terraform data</p>
</li>
</ul>
<h3 id="heading-example-2">🧠 Example</h3>
<pre><code class="lang-plaintext">locals {
  data = {
    app = "web"
    port = 8080
  }

  yaml_string = yamlencode(local.data)
  yaml_parsed = yamldecode(local.yaml_string)
}
</code></pre>
<h3 id="heading-output-2">🧾 Output</h3>
<pre><code class="lang-plaintext">yaml_string:
app: web
port: 8080
yaml_parsed: { app = "web", port = 8080 }
</code></pre>
<h3 id="heading-real-world-use-case-2">💡 Real-world use case</h3>
<p>Generate a Kubernetes deployment file dynamically:</p>
<pre><code class="lang-plaintext">resource "local_file" "k8s_manifest" {
  content = yamlencode({
    apiVersion = "v1"
    kind       = "Pod"
    metadata = { name = "nginx" }
    spec = {
      containers = [{
        name  = "nginx"
        image = "nginx:latest"
      }]
    }
  })
  filename = "${path.module}/nginx.yaml"
}
</code></pre>
<hr />
<h3 id="heading-4-csvdecode">📋 4. <code>csvdecode()</code></h3>
<p><strong>📘 Description</strong></p>
<p>Parses a CSV-formatted string into a list of maps.<br />This is helpful when reading structured data from CSV files like user lists, IPs, or configuration tables.</p>
<h3 id="heading-example-3">🧠 Example</h3>
<pre><code class="lang-plaintext">locals {
  csv_data = csvdecode("name,age\nAlice,30\nBob,25")
}
</code></pre>
<h3 id="heading-output-3">🧾 Output</h3>
<pre><code class="lang-plaintext">[
  { name = "Alice", age = "30" },
  { name = "Bob",   age = "25" }
]
</code></pre>
<h3 id="heading-real-world-use-case-3">💡 Real-world use case</h3>
<p>Imagine you have a CSV file of instance names and sizes:</p>
<pre><code class="lang-plaintext">name,type
web,t2.micro
db,t3.micro
</code></pre>
<p>You can load and use it:</p>
<pre><code class="lang-plaintext">locals {
  instances = csvdecode(file("${path.module}/instances.csv"))
}

resource "aws_instance" "csv_based" {
  for_each = { for row in local.instances : row.name =&gt; row }

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = each.value.type
  tags = {
    Name = each.value.name
  }
}
</code></pre>
<p>✅ Automatically creates EC2 instances from your CSV!</p>
<hr />
<h3 id="heading-cryptographic-functions">🔑 <strong>Cryptographic Functions</strong></h3>
<p>These functions deal with <strong>data integrity, security, and uniqueness</strong>.<br />They are especially important when:</p>
<ul>
<li><p>creating hashed passwords</p>
</li>
<li><p>verifying file content integrity</p>
</li>
<li><p>generating stable resource names</p>
</li>
</ul>
<hr />
<h3 id="heading-1-md5">🔹 1. <code>md5()</code></h3>
<p><strong>📘 Description</strong></p>
<p>Generates a 128-bit MD5 hash of a string (commonly used for file integrity).</p>
<pre><code class="lang-plaintext">md5("Terraform") # =&gt; "e5bdf6e80e63f03d7a26dd9a88a234f0"
</code></pre>
<p>💡 Example use:</p>
<pre><code class="lang-plaintext">locals {
  checksum = md5(file("${path.module}/app.zip"))
}
</code></pre>
<p>→ Useful for detecting when a file changes.</p>
<hr />
<h3 id="heading-2-sha1-sha256-sha512">🔹 2. <code>sha1()</code>, <code>sha256()</code>, <code>sha512()</code></h3>
<p><strong>📘 Description</strong></p>
<p>These are stronger cryptographic hash functions —<br />they generate longer, more secure digests than MD5.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Hash Length</td><td>Typical Use</td></tr>
</thead>
<tbody>
<tr>
<td>sha1()</td><td>160-bit</td><td>Deprecated but still used for simple checks</td></tr>
<tr>
<td>sha256()</td><td>256-bit</td><td>Most common for data integrity</td></tr>
<tr>
<td>sha512()</td><td>512-bit</td><td>For maximum security</td></tr>
</tbody>
</table>
</div><h3 id="heading-example-4">🧠 Example</h3>
<pre><code class="lang-plaintext">locals {
  data = "Hello, Terraform!"
  sha1_value   = sha1(local.data)
  sha256_value = sha256(local.data)
  sha512_value = sha512(local.data)
}
</code></pre>
<hr />
<h3 id="heading-3-bcrypt">🔹 3. <code>bcrypt()</code></h3>
<p><strong>📘 Description</strong></p>
<p>Generates a <strong>secure hash for passwords</strong> using the bcrypt algorithm.<br />Unlike SHA, bcrypt hashes are salted — even if the same password is hashed twice, the result is different.</p>
<p><strong>🧠 Example</strong></p>
<pre><code class="lang-plaintext">locals {
  password = "supersecret"
  bcrypt_hash = bcrypt(local.password)
}
</code></pre>
<p>Output → <code>$2a$10$Jd2P8y...</code> (random each time)</p>
<p>💡 Common in user provisioning:</p>
<pre><code class="lang-plaintext">resource "aws_iam_user_login_profile" "user" {
  user    = "admin"
  pgp_key = "keybase:exampleuser"
  password_reset_required = false
  password = bcrypt("MySecurePassword123")
}
</code></pre>
<hr />
<h1 id="heading-summary">🧠 Summary</h1>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Purpose</td><td>Example Use</td></tr>
</thead>
<tbody>
<tr>
<td>base64encode / decode</td><td>Convert text to/from Base64</td><td>EC2 user_data</td></tr>
<tr>
<td>jsonencode / decode</td><td>Convert between Terraform maps and JSON</td><td>Store configs in S3</td></tr>
<tr>
<td>urlencode</td><td>Prepare strings for API URLs</td><td>API queries</td></tr>
<tr>
<td>yamlencode / decode</td><td>Work with YAML config files</td><td>Kubernetes manifests</td></tr>
<tr>
<td>csvdecode</td><td>Parse CSV into list of maps</td><td>Bulk instance creation</td></tr>
<tr>
<td>md5 / sha1 / sha256 / sha512</td><td>Create hashes for integrity/security</td><td>Verify file changes</td></tr>
<tr>
<td>bcrypt</td><td>Secure password hashing</td><td>IAM user creation</td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-date-and-time-functions">📅 <strong>Date and Time Functions</strong></h3>
<p>These are used to manage timestamps — such as for naming resources with deployment time, scheduling, auditing, or versioning.</p>
<hr />
<h3 id="heading-1-timestamp">🕒 1. <code>timestamp()</code></h3>
<p><strong>Purpose:</strong><br />Returns the current UTC timestamp in ISO 8601 format (e.g., <code>"2025-10-22T08:00:00Z"</code>).</p>
<pre><code class="lang-plaintext">locals {
  now = timestamp()
}
</code></pre>
<p><strong>Example Output:</strong></p>
<pre><code class="lang-plaintext">"2025-10-22T08:00:00Z"
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Tagging resources with deployment time.</p>
</li>
<li><p>Creating unique names for artifacts.</p>
</li>
<li><p>Logging when a deployment occurred.</p>
</li>
</ul>
<pre><code class="lang-plaintext">tags = {
  CreatedAt = timestamp()
}
</code></pre>
<hr />
<h3 id="heading-2-formatdateformat-timestamp">🗓️ 2. <code>formatdate(format, timestamp)</code></h3>
<p><strong>Purpose:</strong><br />Formats a timestamp into a human-readable string using custom format tokens.</p>
<pre><code class="lang-plaintext">locals {
  formatted1 = formatdate("DD MMM YYYY hh:mm:ss", timestamp())
  formatted2 = formatdate("YYYY-MM-DD", timestamp())
}
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-plaintext">"22 Oct 2025 14:30:00"
"2025-10-22"
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Version tagging (e.g., <code>v2025-10-22</code>)</p>
</li>
<li><p>Logging or backup filenames</p>
</li>
<li><p>Human-readable labels in outputs</p>
</li>
</ul>
<pre><code class="lang-plaintext">output "build_version" {
  value = "v${formatdate("YYYYMMDD-hhmm", timestamp())}"
}
</code></pre>
<hr />
<h3 id="heading-3-timeaddtimestamp-duration">⏩ 3. <code>timeadd(timestamp, duration)</code></h3>
<p><strong>Purpose:</strong><br />Adds a duration to a timestamp (supports seconds, minutes, hours, days, weeks).</p>
<pre><code class="lang-plaintext">locals {
  now        = timestamp()
  tomorrow   = timeadd(local.now, "24h")
  next_week  = timeadd(local.now, "168h") # 7 days
}
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Setting expiry times (e.g., certificate expiry, temporary access)</p>
</li>
<li><p>Scheduling delayed actions</p>
</li>
</ul>
<hr />
<h3 id="heading-filesystem-functions">📁 <strong>Filesystem Functions</strong></h3>
<p>These allow Terraform to interact with files on your local filesystem — reading, checking, finding, or rendering templates.</p>
<hr />
<h3 id="heading-1-filepath">📄 1. <code>file(path)</code></h3>
<p><strong>Purpose:</strong><br />Reads the contents of a file as a string.</p>
<pre><code class="lang-plaintext">locals {
  script_content = file("${path.module}/scripts/init.sh")
}
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Load shell scripts, config files, or policies to inject into resources.</p>
</li>
<li><p>Pass user_data to EC2 or container startup scripts.</p>
</li>
</ul>
<hr />
<h3 id="heading-2-fileexistspath">✅ 2. <code>fileexists(path)</code></h3>
<p><strong>Purpose:</strong><br />Checks whether a file exists and returns <code>true</code> or <code>false</code>.</p>
<pre><code class="lang-plaintext">locals {
  has_config = fileexists("${path.module}/config.yaml")
}
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Conditional logic to include optional files.</p>
</li>
<li><p>Avoid Terraform errors when optional inputs are missing.</p>
</li>
</ul>
<hr />
<h3 id="heading-3-filesetdirectory-pattern">📂 3. <code>fileset(directory, pattern)</code></h3>
<p><strong>Purpose:</strong><br />Returns a list of filenames in a directory matching a pattern (e.g., all <code>.sh</code> files).</p>
<pre><code class="lang-plaintext">locals {
  all_scripts = fileset(path.module, "scripts/*.sh")
}
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Automatically include all scripts or templates in a directory.</p>
</li>
<li><p>Create multiple resources (e.g., upload all scripts to S3).</p>
</li>
</ul>
<hr />
<h3 id="heading-4-templatefilepath-vars">🧩 4. <code>templatefile(path, vars)</code></h3>
<p><strong>Purpose:</strong><br />Reads a file and replaces placeholders with variable values.<br />Placeholders use the <code>${var_name}</code> syntax.</p>
<pre><code class="lang-plaintext">locals {
  user_data = templatefile("${path.module}/templates/user-data.sh", {
    hostname = "web-server"
    port     = 8080
  })
}
</code></pre>
<p><strong>If</strong> <a target="_blank" href="http://user-data.sh"><code>user-data.sh</code></a> contains:</p>
<pre><code class="lang-plaintext">#!/bin/bash
echo "Starting ${hostname} on port ${port}"
</code></pre>
<p>✅ <strong>Output:</strong></p>
<pre><code class="lang-plaintext">#!/bin/bash
echo "Starting web-server on port 8080"
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li>Dynamically generate scripts, configuration files, or manifest templates.</li>
</ul>
<hr />
<h3 id="heading-5-path-utility-functions">🧭 5. Path Utility Functions</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Description</td><td>Example</td><td>Output</td></tr>
</thead>
<tbody>
<tr>
<td><code>dirname(path)</code></td><td>Returns the directory portion</td><td><code>dirname("/opt/data/file.txt")</code></td><td><code>/opt/data</code></td></tr>
<tr>
<td><code>basename(path)</code></td><td>Returns filename</td><td><code>basename("/opt/data/file.txt")</code></td><td><code>file.txt</code></td></tr>
<tr>
<td><code>abspath(path)</code></td><td>Converts relative path to absolute</td><td><code>abspath("./relative")</code></td><td><code>/home/user/project/relative</code></td></tr>
<tr>
<td><code>pathexpand(path)</code></td><td>Expands <code>~</code> to home directory</td><td><code>pathexpand("~/data")</code></td><td><code>/home/username/data</code></td></tr>
</tbody>
</table>
</div><p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Managing file paths in reusable modules.</p>
</li>
<li><p>Normalizing paths across systems.</p>
</li>
</ul>
<hr />
<h3 id="heading-ip-network-functions">🌐 <strong>IP Network Functions</strong></h3>
<p>These functions help manipulate and calculate IP network addresses — crucial for <strong>VPC</strong>, <strong>subnets</strong>, and <strong>CIDR management</strong> in AWS, Azure, or GCP.</p>
<hr />
<h3 id="heading-1-cidrhostcidr-hostnum">🌍 1. <code>cidrhost(cidr, hostnum)</code></h3>
<p><strong>Purpose:</strong><br />Returns a specific host IP address from a given subnet.</p>
<pre><code class="lang-plaintext">locals {
  vpc_cidr = "10.0.0.0/16"
  first_ip = cidrhost(local.vpc_cidr, 0)
  second_ip = cidrhost(local.vpc_cidr, 1)
}
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-plaintext">"10.0.0.0"
"10.0.0.1"
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li><p>Assign static IPs in subnets.</p>
</li>
<li><p>Generate predictable host IPs.</p>
</li>
</ul>
<hr />
<h3 id="heading-2-cidrnetmaskcidr">🧱 2. <code>cidrnetmask(cidr)</code></h3>
<p><strong>Purpose:</strong><br />Returns the netmask of a CIDR block.</p>
<pre><code class="lang-plaintext">cidrnetmask("10.0.0.0/16") # =&gt; "255.255.0.0"
</code></pre>
<p>✅ <strong>Use Case:</strong> Documentation or tagging (helps identify subnet size easily).</p>
<hr />
<h3 id="heading-3-cidrsubnetcidr-newbits-netnum">🧩 3. <code>cidrsubnet(cidr, newbits, netnum)</code></h3>
<p><strong>Purpose:</strong><br />Subdivides a larger CIDR into smaller subnet blocks.</p>
<pre><code class="lang-plaintext">locals {
  subnet1 = cidrsubnet("10.0.0.0/16", 8, 0) # 10.0.0.0/24
  subnet2 = cidrsubnet("10.0.0.0/16", 8, 1) # 10.0.1.0/24
}
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li>Create multiple subnets dynamically for different environments or AZs.</li>
</ul>
<hr />
<h3 id="heading-4-cidrsubnetscidr-newbits">🧮 4. <code>cidrsubnets(cidr, newbits...)</code></h3>
<p><strong>Purpose:</strong><br />Generates multiple subnets at once from one parent network.</p>
<pre><code class="lang-plaintext">locals {
  subnets = cidrsubnets("10.0.0.0/16", 8, 8, 8)
}
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="lang-plaintext">["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
</code></pre>
<p>✅ <strong>Use Cases:</strong></p>
<ul>
<li>Automatically allocate subnets for each application layer.</li>
</ul>
<hr />
<h3 id="heading-hands-on-lab">🧪 Hands-On Lab</h3>
<h3 id="heading-project-structure">📁<strong>project structure</strong></h3>
<pre><code class="lang-plaintext">terraform-day15/
├── main.tf
├── variables.tf
├── locals.tf
├── outputs.tf
├── scripts/
│   └── init.sh
├── templates/
│   └── user-data.sh
└── config.yaml
</code></pre>
<p>This lab uses <strong>Date/Time</strong>, <strong>Filesystem</strong>, <strong>IP Network</strong>, and <strong>Type Conversion</strong> functions all together.</p>
<hr />
<h3 id="heading-scenario">🧠 <strong>Scenario</strong></h3>
<p>We’ll simulate deploying a <strong>web server</strong> into a <strong>generated subnet</strong> inside a <strong>VPC</strong>.<br />Terraform will:</p>
<ul>
<li><p>Calculate subnets using IP functions. 🧮</p>
</li>
<li><p>Generate timestamps and version tags using Date/Time functions ⏱️</p>
</li>
<li><p>Read files and templates using Filesystem functions 📁</p>
</li>
<li><p>Use Type Conversion functions for safe, flexible data handling 🔄</p>
</li>
</ul>
<hr />
<h3 id="heading-1-variablestfhttpvariablestf">1️⃣ <a target="_blank" href="http://variables.tf">variables.tf</a></h3>
<pre><code class="lang-plaintext">variable "project_name" {
  description = "Project name for tagging and resource naming"
  type        = string
  default     = "terraform-lab"
}

variable "region" {
  description = "AWS region to deploy to"
  type        = string
  default     = "us-east-1"
}

variable "vpc_cidr" {
  description = "CIDR block for the VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "enable_public_access" {
  description = "Allow public access to the instance?"
  type        = string
  default     = "true" # as string for demonstration of tobool()
}
</code></pre>
<hr />
<h3 id="heading-2-localstfhttplocalstf">2️⃣ <a target="_blank" href="http://locals.tf">locals.tf</a></h3>
<pre><code class="lang-plaintext">locals {
  # -------------------------
  # 📅 Date &amp; Time Functions
  # -------------------------
  current_time   = timestamp()
  formatted_time = formatdate("YYYY-MM-DD hh:mm:ss", local.current_time)
  build_version  = formatdate("YYYYMMDD-hhmm", local.current_time)
  expire_time    = timeadd(local.current_time, "168h") # +7 days

  # -------------------------
  # 🌐 IP Network Functions
  # -------------------------
  vpc_cidr  = var.vpc_cidr
  subnet1   = cidrsubnet(local.vpc_cidr, 8, 0) # "10.0.0.0/24"
  subnet2   = cidrsubnet(local.vpc_cidr, 8, 1) # "10.0.1.0/24"
  first_ip  = cidrhost(local.subnet1, 10)
  netmask   = cidrnetmask(local.vpc_cidr)

  # -------------------------
  # 📁 Filesystem Functions
  # -------------------------
  script_content = file("${path.module}/scripts/init.sh")
  has_config     = fileexists("${path.module}/config.yaml")
  all_scripts    = fileset(path.module, "scripts/*.sh")

  # Render a user-data template with variables
  user_data = templatefile("${path.module}/templates/user-data.sh", {
    hostname = "${var.project_name}-server"
    port     = 8080
  })

  # -------------------------
  # 🔄 Type Conversion Functions
  # -------------------------
  is_public   = tobool(var.enable_public_access)
  number_test = try(tonumber("123"), 0)
  is_number   = can(tonumber("abc")) # false

  # Convert set → list → string for display
  tags_set  = toset(["app", "terraform", "infra", "app"])
  tags_list = tolist(local.tags_set)
  tags_str  = tostring(join(",", local.tags_list))
}
</code></pre>
<hr />
<h3 id="heading-3-maintfhttpmaintf">3️⃣ <a target="_blank" href="http://main.tf">main.tf</a></h3>
<p><em>(This simulates deployment logic — no real AWS resources needed, you can run this locally and observe outputs.)</em></p>
<pre><code class="lang-plaintext">terraform {
  required_version = "&gt;= 1.6.0"
}

provider "aws" {
  region = var.region
}

# Example local resource to simulate usage of all functions
resource "null_resource" "example" {
  triggers = {
    build_version = local.build_version
    subnet        = local.subnet1
    netmask       = local.netmask
    has_config    = tostring(local.has_config)
    tags          = local.tags_str
  }

  provisioner "local-exec" {
    command = "echo '🚀 Deploying ${var.project_name} version ${local.build_version}'"
  }
}

# Save rendered user-data to file for demonstration
resource "local_file" "rendered_user_data" {
  content  = local.user_data
  filename = "${path.module}/rendered-user-data.sh"
}
</code></pre>
<hr />
<h3 id="heading-4-outputstfhttpoutputstf">4️⃣ <a target="_blank" href="http://outputs.tf">outputs.tf</a></h3>
<pre><code class="lang-plaintext">output "📅_date_time" {
  value = {
    now          = local.current_time
    formatted    = local.formatted_time
    version_tag  = local.build_version
    expires_at   = local.expire_time
  }
}

output "🌐_network_details" {
  value = {
    vpc_cidr  = local.vpc_cidr
    subnet1   = local.subnet1
    subnet2   = local.subnet2
    first_ip  = local.first_ip
    netmask   = local.netmask
  }
}

output "📁_filesystem_info" {
  value = {
    config_exists = local.has_config
    scripts_found = local.all_scripts
    user_data     = local.user_data
  }
}

output "🔄_type_conversions" {
  value = {
    is_public   = local.is_public
    number_test = local.number_test
    is_number   = local.is_number
    tags        = local.tags_str
  }
}
</code></pre>
<hr />
<h3 id="heading-5-scriptsinitshhttpinitsh">5️⃣ scripts/<a target="_blank" href="http://init.sh">init.sh</a></h3>
<pre><code class="lang-plaintext">#!/bin/bash
echo "Initializing system..."
echo "Date: $(date)"
</code></pre>
<hr />
<h3 id="heading-6-templatesuser-datashhttpuser-datash">6️⃣ templates/<a target="_blank" href="http://user-data.sh">user-data.sh</a></h3>
<pre><code class="lang-plaintext">#!/bin/bash
echo "🚀 Launching ${hostname}"
echo "Listening on port ${port}"
</code></pre>
<hr />
<h3 id="heading-7-configyaml">7️⃣ config.yaml</h3>
<pre><code class="lang-plaintext">project: terraform-lab
environment: dev
owner: example@example.com
</code></pre>
<h3 id="heading-run">Run</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init
<span class="hljs-comment"># Use console to test functions</span>
terraform console
<span class="hljs-comment"># Try these:</span>
&gt; cidrsubnet(<span class="hljs-string">"10.0.0.0/16"</span>, 8, 0)
<span class="hljs-string">"10.0.0.0/24"</span>
&gt; cidrhost(<span class="hljs-string">"10.0.1.0/24"</span>, 5)
<span class="hljs-string">"10.0.1.5"</span>
&gt; format(<span class="hljs-string">"%s-%03d"</span>, <span class="hljs-string">"web"</span>, 5)
<span class="hljs-string">"web-005"</span>
&gt; timestamp()
<span class="hljs-string">"2025-10-22T09:38:22Z"</span>
&gt; formatdate(<span class="hljs-string">"DD-MM-YYYY"</span>, timestamp())
<span class="hljs-string">"22-10-2025"</span>
&gt; md5(<span class="hljs-string">"terraform"</span>)
<span class="hljs-string">"1b1ed905d54c18e3dd8828986c14be17"</span>
&gt; range(5)
tolist([
  0,
  1,
  2,
  3,
  4,
])
&gt; <span class="hljs-built_in">exit</span>
<span class="hljs-comment"># Apply</span>
terraform apply -auto-approve
<span class="hljs-comment"># View outputs</span>
terraform output

<span class="hljs-comment">#Output look like this </span>
null_resource.example: Creating...
local_file.rendered_user_data: Creating...
null_resource.example: Provisioning with <span class="hljs-string">'local-exec'</span>...
local_file.rendered_user_data: Creation complete after 0s [id=2b67ae43a0bde8eaf87b28a86c7c9b9bf94cd3d5]
null_resource.example (local-exec): Executing: [<span class="hljs-string">"/bin/sh"</span> <span class="hljs-string">"-c"</span> <span class="hljs-string">"echo '🚀 Deploying terraform-lab version 20251022-0940'"</span>]
null_resource.example (local-exec): 🚀 Deploying terraform-lab version 20251022-0940
null_resource.example: Creation complete after 0s [id=6127148458059718850]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:
date_time = {
  <span class="hljs-string">"expires_at"</span> = <span class="hljs-string">"2025-10-29T09:40:25Z"</span>
  <span class="hljs-string">"formatted"</span> = <span class="hljs-string">"2025-10-22 09:40:25"</span>
  <span class="hljs-string">"now"</span> = <span class="hljs-string">"2025-10-22T09:40:25Z"</span>
  <span class="hljs-string">"version_tag"</span> = <span class="hljs-string">"20251022-0940"</span>
}
filesystem_info = {
  <span class="hljs-string">"config_exists"</span> = <span class="hljs-literal">true</span>
  <span class="hljs-string">"scripts_found"</span> = toset([
    <span class="hljs-string">"scripts/init.sh"</span>,
  ])
  <span class="hljs-string">"user_data"</span> = &lt;&lt;-EOT
  <span class="hljs-comment">#!/bin/bash</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"🚀 Launching terraform-lab-server"</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Listening on port 8080"</span>
  EOT
}
network_details = {
  <span class="hljs-string">"first_ip"</span> = <span class="hljs-string">"10.0.0.10"</span>
  <span class="hljs-string">"netmask"</span> = <span class="hljs-string">"255.255.0.0"</span>
  <span class="hljs-string">"subnet1"</span> = <span class="hljs-string">"10.0.0.0/24"</span>
  <span class="hljs-string">"subnet2"</span> = <span class="hljs-string">"10.0.1.0/24"</span>
  <span class="hljs-string">"vpc_cidr"</span> = <span class="hljs-string">"10.0.0.0/16"</span>
}
type_conversions = {
  <span class="hljs-string">"is_number"</span> = <span class="hljs-literal">false</span>
  <span class="hljs-string">"is_public"</span> = <span class="hljs-literal">true</span>
  <span class="hljs-string">"number_test"</span> = 123
  <span class="hljs-string">"tags"</span> = <span class="hljs-string">"app,infra,terraform"</span>
}

<span class="hljs-comment"># Clean up</span>
terraform destroy -auto-approve
</code></pre>
<h3 id="heading-summary-1">📝 Summary</h3>
<p>Today you learned:</p>
<ul>
<li><p>✅ Encoding functions (base64, json, yaml)</p>
</li>
<li><p>✅ Crypto functions (md5, sha256, bcrypt)</p>
</li>
<li><p>✅ Date/time functions (timestamp, formatdate)</p>
</li>
<li><p>✅ Network functions (cidrsubnet, cidrhost)</p>
</li>
<li><p>✅ Filesystem functions (file, templatefile)</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 16: Dynamic Blocks for Flexible Resources</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Master dynamic blocks</p>
</li>
<li><p>Create flexible security groups</p>
</li>
<li><p>Build dynamic ingress/egress rules</p>
</li>
<li><p>Use for_each in dynamic blocks</p>
</li>
<li><p>Handle complex nested blocks</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-14-built-in-functions-in-terraform-part-i">← Day 14: Conditionals &amp; Logic</a> | <a target="_blank" href="day16.md">Day 16: Dynamic Blocks →</a></p>
<hr />
<p><em>Remember: Functions are powerful tools for data transformation and manipulation!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 14: Built-in Functions in Terraform Part-I]]></title><description><![CDATA[Today, we’ll explore Terraform functions — powerful helpers that make your infrastructure more flexible and smart.
Terraform functions allow you to manipulate data, perform calculations, handle text, work with networks, and much more — all inside you...]]></description><link>https://stackopsdiary.site/day-14-built-in-functions-in-terraform-part-i</link><guid isPermaLink="true">https://stackopsdiary.site/day-14-built-in-functions-in-terraform-part-i</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Tue, 21 Oct 2025 12:30:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761037221893/2cc744ab-6e05-47f8-8102-f99daedf9ba3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today, we’ll explore <strong>Terraform functions</strong> — powerful helpers that make your infrastructure more flexible and smart.</p>
<p>Terraform functions allow you to <strong>manipulate data, perform calculations, handle text, work with networks</strong>, and much more — all inside your configuration files.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<p>By the end of this lesson, you’ll be able to:</p>
<ul>
<li><p>✅ Master essential Terraform functions</p>
</li>
<li><p>✅ Understand string functions</p>
</li>
<li><p>✅ Understand numeric functions</p>
</li>
<li><p>✅ Apply them in real-world Terraform projects</p>
</li>
</ul>
<hr />
<h3 id="heading-numeric-functions-explained-for-beginners">🔢 Numeric Functions — Explained for Beginners</h3>
<p>Terraform numeric functions help you perform <strong>mathematical calculations</strong> directly in your configuration files.<br />You can use them to calculate:</p>
<ul>
<li><p>How many instances or subnets to create</p>
</li>
<li><p>How large a storage volume should be</p>
</li>
<li><p>How to round or constrain numeric inputs</p>
</li>
</ul>
<p>Let’s go through each function carefully. 👇</p>
<hr />
<h2 id="heading-mathematical-operations">🧮 <strong>Mathematical Operations</strong></h2>
<p>Here’s a breakdown of each numeric function from the example:</p>
<hr />
<h3 id="heading-1-absnumber-absolute-value">🧩 1. <code>abs(number)</code> — <strong>Absolute Value</strong></h3>
<blockquote>
<p>The absolute value means “ignore the negative sign.”</p>
</blockquote>
<pre><code class="lang-plaintext">locals {
  absolute = abs(-15)
}
</code></pre>
<p>📘 <strong>Explanation:</strong><br />If the number is negative, <code>abs()</code> turn it into a positive number.<br />If it’s already positive, it stays the same.</p>
<p><strong>Example use case:</strong><br />If you’re calculating a difference between two values (like used vs. available storage), you can use it <code>abs()</code> to make sure the result is always positive.</p>
<pre><code class="lang-plaintext">locals {
  storage_difference = abs(50 - 100)  # =&gt; 50
}
</code></pre>
<hr />
<h3 id="heading-2-ceilnumber-round-up">🧩 2. <code>ceil(number)</code> — <strong>Round Up</strong></h3>
<blockquote>
<p>Always rounds a decimal number <strong>up to the nearest whole number</strong>.</p>
</blockquote>
<pre><code class="lang-plaintext">locals {
  ceiling = ceil(5.1)  # =&gt; 6
}
</code></pre>
<p>📘 <strong>Example:</strong></p>
<ul>
<li><p><code>ceil(3.1)</code> → <code>4</code></p>
</li>
<li><p><code>ceil(7.9)</code> → <code>8</code></p>
</li>
</ul>
<p>💡 <strong>Use case:</strong><br />You can use this when dividing resources and you want to <strong>avoid losing part of a unit</strong>.<br />For example, if each subnet can handle 2 servers, and you have 5 servers → <code>ceil(5 / 2)</code> = 3 subnets needed.</p>
<hr />
<h3 id="heading-3-floornumber-round-down">🧩 3. <code>floor(number)</code> — <strong>Round Down</strong></h3>
<blockquote>
<p>Always rounds a decimal number <strong>down to the nearest whole number</strong>.</p>
</blockquote>
<pre><code class="lang-plaintext">locals {
  floored = floor(5.9)  # =&gt; 5
}
</code></pre>
<p>📘 <strong>Example:</strong></p>
<ul>
<li><p><code>floor(4.9)</code> → <code>4</code></p>
</li>
<li><p><code>floor(6.3)</code> → <code>6</code></p>
</li>
</ul>
<p>💡 <strong>Use case:</strong><br />If you need to calculate full “groups” of resources and you can’t have fractions (like 5.9 groups → only 5 real groups).</p>
<hr />
<h3 id="heading-4-maxa-b-c-find-maximum-value">🧩 4. <code>max(a, b, c, …)</code> — <strong>Find Maximum Value</strong></h3>
<blockquote>
<p>Returns the <strong>largest number</strong> from a list of numbers.</p>
</blockquote>
<pre><code class="lang-plaintext">locals {
  maximum = max(5, 12, 9)  # =&gt; 12
}
</code></pre>
<p>📘 <strong>Example:</strong></p>
<ul>
<li><p><code>max(3, 10, 7)</code> → <code>10</code></p>
</li>
<li><p><code>max(0, -5, 8)</code> → <code>8</code></p>
</li>
</ul>
<p>💡 <strong>Use case:</strong><br />You can use it <code>max()</code> to make sure something never goes below a minimum—for example, the number of servers should be at least 1.</p>
<hr />
<h3 id="heading-5-mina-b-c-find-minimum-value">🧩 5. <code>min(a, b, c, …)</code> — <strong>Find Minimum Value</strong></h3>
<blockquote>
<p>Returns the <strong>smallest number</strong>.</p>
</blockquote>
<pre><code class="lang-plaintext">locals {
  minimum = min(5, 12, 9)  # =&gt; 5
}
</code></pre>
<p>💡 <strong>Use case:</strong><br />When you want to limit a maximum value—for example, if a user requests 10 subnets but you only want to allow up to 6:</p>
<pre><code class="lang-plaintext">locals {
  subnet_count = min(var.requested_subnets, 6)
}
</code></pre>
<hr />
<h3 id="heading-6-powbase-exponent-power-function">🧩 6. <code>pow(base, exponent)</code> — <strong>Power Function</strong></h3>
<blockquote>
<p>Raises a number to a power.<br />(Mathematically: base^exponent)</p>
</blockquote>
<pre><code class="lang-plaintext">locals {
  power = pow(2, 3)  # =&gt; 8  (2×2×2)
}
</code></pre>
<p>📘 <strong>Example:</strong></p>
<ul>
<li><p><code>pow(3, 2)</code> → <code>9</code></p>
</li>
<li><p><code>pow(10, 3)</code> → <code>1000</code></p>
</li>
</ul>
<p>💡 <strong>Use case:</strong><br />Useful when you need exponential scaling—e.g., storage or CPU size that doubles with every increase in instance count.</p>
<hr />
<h3 id="heading-7-logbase-number-logarithm">🧩 7. <code>log(base, number)</code> — <strong>Logarithm</strong></h3>
<blockquote>
<p>Calculates how many times you multiply the <strong>base</strong> to get the <strong>number</strong>.</p>
</blockquote>
<pre><code class="lang-plaintext">locals {
  logarithm = log(10, 100)  # =&gt; 2
}
</code></pre>
<p>📘 <strong>Explanation:</strong><br />10² = 100 → That’s why <code>log(10, 100)</code> = 2.<br />It’s like saying, “How many times must I multiply 10 to get 100?”</p>
<p>💡 <strong>Use case:</strong><br />You might use it <code>log()</code> when scaling values logarithmically—for instance, computing tiers or power-of-two sizes.</p>
<hr />
<h3 id="heading-8-signumnumber-get-the-sign">🧩 8. <code>signum(number)</code> — <strong>Get the Sign</strong></h3>
<blockquote>
<p>Returns:</p>
</blockquote>
<ul>
<li><p><code>1</code> if number is positive</p>
</li>
<li><p><code>-1</code> if negative</p>
</li>
<li><p><code>0</code> if zero</p>
</li>
</ul>
<pre><code class="lang-plaintext">locals {
  sign = signum(-15)  # =&gt; -1
}
</code></pre>
<p>💡 <strong>Use case:</strong><br />When you want to check the direction or “trend” of a value.<br />Example:</p>
<pre><code class="lang-plaintext">locals {
  delta = var.new_value - var.old_value
  direction = signum(local.delta)
}
# If new_value &gt; old_value → +1 (increase)
# If new_value &lt; old_value → -1 (decrease)
# If same → 0
</code></pre>
<hr />
<h2 id="heading-summary-table">🧠 Summary Table</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Meaning</td><td>Example</td><td>Result</td></tr>
</thead>
<tbody>
<tr>
<td><code>abs(x)</code></td><td>Absolute value</td><td><code>abs(-10)</code></td><td><code>10</code></td></tr>
<tr>
<td><code>ceil(x)</code></td><td>Round up</td><td><code>ceil(2.1)</code></td><td><code>3</code></td></tr>
<tr>
<td><code>floor(x)</code></td><td>Round down</td><td><code>floor(4.9)</code></td><td><code>4</code></td></tr>
<tr>
<td><code>max(a,b,c)</code></td><td>Largest value</td><td><code>max(2,7,5)</code></td><td><code>7</code></td></tr>
<tr>
<td><code>min(a,b,c)</code></td><td>Smallest value</td><td><code>min(2,7,5)</code></td><td><code>2</code></td></tr>
<tr>
<td><code>pow(a,b)</code></td><td>a^b</td><td><code>pow(2,3)</code></td><td><code>8</code></td></tr>
<tr>
<td><code>log(base,num)</code></td><td>Logarithm</td><td><code>log(10,100)</code></td><td><code>2</code></td></tr>
<tr>
<td><code>signum(x)</code></td><td>Sign of number</td><td><code>signum(-15)</code></td><td><code>-1</code></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-practical-terraform-example">🧰 Practical Terraform Example</h2>
<p>Now, let’s look at the <strong>real Terraform snippet</strong> and explain each part 👇</p>
<pre><code class="lang-plaintext">variable "instance_count" {
  type = number
}

locals {
  # Ensure minimum of 1 instance
  final_count = max(1, var.instance_count)

  # Calculate storage based on instance count
  total_storage_gb = pow(2, ceil(log(var.instance_count, 2)))

  # Round up subnet count
  subnet_count = ceil(var.instance_count / 2.0)
}
</code></pre>
<hr />
<h3 id="heading-step-by-step-explanation">💡 Step-by-step Explanation</h3>
<h4 id="heading-1-finalcount-max1-varinstancecount">1️⃣ <code>final_count = max(1, var.instance_count)</code></h4>
<p>Ensures that even if someone sets <code>instance_count = 0</code>, Terraform will still create at least <strong>1 instance</strong>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>instance_count</td><td>final_count</td></tr>
</thead>
<tbody>
<tr>
<td>0</td><td>1</td></tr>
<tr>
<td>1</td><td>1</td></tr>
<tr>
<td>3</td><td>3</td></tr>
<tr>
<td>5</td><td>5</td></tr>
</tbody>
</table>
</div><hr />
<h4 id="heading-2-totalstoragegb-pow2-ceillogvarinstancecount-2">2️⃣ <code>total_storage_gb = pow(2, ceil(log(var.instance_count, 2)))</code></h4>
<p>This line automatically scales storage capacity using a <strong>power of two</strong> rule.</p>
<p>Let’s break it down:</p>
<ul>
<li><p><code>log(var.instance_count, 2)</code> → finds the power needed to reach <code>instance_count</code> with base 2</p>
</li>
<li><p><code>ceil()</code> → rounds that up</p>
</li>
<li><p><code>pow(2, …)</code> → calculates 2 raised to that rounded number</p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td>instance_count</td><td>log2</td><td>ceil</td><td>pow(2,ceil)</td><td>total_storage_gb</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>0</td><td>0</td><td>1</td><td>1</td></tr>
<tr>
<td>2</td><td>1</td><td>1</td><td>2</td><td>2</td></tr>
<tr>
<td>3</td><td>1.58</td><td>2</td><td>4</td><td>4</td></tr>
<tr>
<td>5</td><td>2.32</td><td>3</td><td>8</td><td>8</td></tr>
</tbody>
</table>
</div><p>🧠 Meaning:<br />If you scale up instances, your total storage scales automatically in powers of two — a common pattern in cloud infrastructure design.</p>
<hr />
<h4 id="heading-3-subnetcount-ceilvarinstancecount-20">3️⃣ <code>subnet_count = ceil(var.instance_count / 2.0)</code></h4>
<p>This means <strong>each subnet will hold 2 instances</strong>, and Terraform will <strong>round up</strong> if there’s an extra one.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>instance_count</td><td>instance_count / 2</td><td>ceil</td><td>subnet_count</td></tr>
</thead>
<tbody>
<tr>
<td>2</td><td>1</td><td>1</td><td>1</td></tr>
<tr>
<td>3</td><td>1.5</td><td>2</td><td>2</td></tr>
<tr>
<td>4</td><td>2</td><td>2</td><td>2</td></tr>
<tr>
<td>5</td><td>2.5</td><td>3</td><td>3</td></tr>
</tbody>
</table>
</div><p>💡 This ensures no instance is left without a subnet.</p>
<hr />
<h2 id="heading-try-it-yourself-in-terraform-console">🧪 Try It Yourself in Terraform Console</h2>
<p>You can test these functions directly in Terraform:</p>
<pre><code class="lang-plaintext">terraform console
&gt; abs(-20)
&gt; ceil(4.3)
&gt; floor(9.8)
&gt; max(1, 9, 5)
&gt; pow(2, 4)
&gt; log(10, 1000)
&gt; signum(-99)
&gt; exit
</code></pre>
<hr />
<h3 id="heading-terraform-string-functions">🔤 Terraform String Functions</h3>
<p>Terraform <strong>string functions</strong> help you <strong>manipulate, search, and format text</strong>.</p>
<p>You’ll use these functions a lot to:</p>
<ul>
<li><p>Build clean <strong>resource names</strong></p>
</li>
<li><p>Format <strong>instance IDs</strong>, <strong>tags</strong>, or <strong>DNS names</strong></p>
</li>
<li><p>Extract or modify text values dynamically</p>
</li>
</ul>
<hr />
<h3 id="heading-what-is-a-string">🎯 What is a String?</h3>
<p>A <strong>string</strong> is simply <strong>text inside quotes</strong>, e.g. <code>"Terraform"</code>, <code>"hello world"</code> ", " <code>"web-server-001"</code></p>
<hr />
<h3 id="heading-1-string-manipulation-functions">🧰 1. <strong>String Manipulation Functions</strong></h3>
<p>These functions change the <strong>appearance</strong> of a string — like converting to lowercase, trimming spaces, or removing parts of it.</p>
<pre><code class="lang-plaintext">locals {
  name = "Terraform"

  lowercase   = lower(name)
  uppercase   = upper(name)
  titlecase   = title("hello world")
  trimmed     = trim("  hello  ", " ")
  trimprefix  = trimprefix("mr-bean", "mr-")
  trimsuffix  = trimsuffix("hello.txt", ".txt")
  cleaned     = trimspace("  hello world  ")
}
</code></pre>
<h3 id="heading-lets-explain-each-one">🔍 Let’s explain each one:</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>What it does</td><td>Example</td><td>Output</td></tr>
</thead>
<tbody>
<tr>
<td><code>lower()</code></td><td>Converts all letters to lowercase</td><td><code>lower("Terraform")</code></td><td><code>"terraform"</code></td></tr>
<tr>
<td><code>upper()</code></td><td>Converts all letters to uppercase</td><td><code>upper("Terraform")</code></td><td><code>"TERRAFORM"</code></td></tr>
<tr>
<td><code>title()</code></td><td>Capitalizes the first letter of each word</td><td><code>title("hello world")</code></td><td><code>"Hello World"</code></td></tr>
<tr>
<td><code>trim()</code></td><td>Removes specific characters from both ends</td><td><code>trim("..hello..", ".")</code></td><td><code>"hello"</code></td></tr>
<tr>
<td><code>trimprefix()</code></td><td>Removes a specific word/prefix from the start</td><td><code>trimprefix("mr-bean", "mr-")</code></td><td><code>"bean"</code></td></tr>
<tr>
<td><code>trimsuffix()</code></td><td>Removes a specific word/suffix from the end</td><td><code>trimsuffix("hello.txt", ".txt")</code></td><td><code>"hello"</code></td></tr>
<tr>
<td><code>trimspace()</code></td><td>Removes spaces/tabs/newlines around text</td><td><code>trimspace(" hi ")</code></td><td><code>"hi"</code></td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-real-world-example">💡 Real-world Example</h3>
<p>Imagine your project name input is messy:</p>
<pre><code class="lang-plaintext">variable "project_name" {
  default = "  MyAPP  "
}
</code></pre>
<p>You can clean and format it for consistent naming:</p>
<pre><code class="lang-plaintext">locals {
  clean_name = lower(trimspace(var.project_name))
}
# "myapp"
</code></pre>
<p>Now your resource names will be uniform and lowercase.</p>
<hr />
<h3 id="heading-2-string-searching-and-replacement">🔍 2. <strong>String Searching and Replacement</strong></h3>
<p>These functions help you <strong>extract</strong>, <strong>replace</strong>, and <strong>format</strong> parts of text.</p>
<pre><code class="lang-plaintext">locals {
  text = "Hello, World!"

  substring = substr(text, 0, 5)
  replaced  = replace(text, "World", "Terraform")
  extracted = regex("([A-Z][a-z]+)", text)
  all_words = regexall("[A-Z][a-z]+", text)
  formatted = format("The answer is %d", 42)
  formatted2 = format("%s-%s-%03d", "web", "server", 5)
  starts     = startswith(text, "Hello")
  ends       = endswith(text, "!")
}
</code></pre>
<p>Let’s look at each in detail 👇</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Meaning</td><td>Example</td><td>Output</td></tr>
</thead>
<tbody>
<tr>
<td><code>substr(string, start, length)</code></td><td>Extract part of a string starting at position</td><td><code>substr("Terraform", 0, 4)</code></td><td><code>"Terr"</code></td></tr>
<tr>
<td><code>replace(string, old, new)</code></td><td>Replace one word with another</td><td><code>replace("hello world", "world", "Terraform")</code></td><td><code>"hello Terraform"</code></td></tr>
<tr>
<td><code>regex(pattern, text)</code></td><td>Extract first match using regex (pattern search)</td><td><code>regex("[A-Z][a-z]+", "Hello World")</code></td><td><code>"Hello"</code></td></tr>
<tr>
<td><code>regexall(pattern, text)</code></td><td>Find all matches using regex</td><td><code>regexall("[A-Z][a-z]+", "Hello World")</code></td><td><code>["Hello", "World"]</code></td></tr>
<tr>
<td><code>format(pattern, values...)</code></td><td>Format text using placeholders</td><td><code>format("%s-%03d", "web", 7)</code></td><td><code>"web-007"</code></td></tr>
<tr>
<td><code>startswith(string, prefix)</code></td><td>Check if text begins with something</td><td><code>startswith("Terraform", "Ter")</code></td><td><code>true</code></td></tr>
<tr>
<td><code>endswith(string, suffix)</code></td><td>Check if text ends with something</td><td><code>endswith("Terraform", "form")</code></td><td><code>true</code></td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-example-breakdown">🧩 Example Breakdown:</h3>
<pre><code class="lang-plaintext">locals {
  text = "Hello, World!"
  substring = substr(text, 0, 5)  # "Hello"
  replaced  = replace(text, "World", "Terraform")  # "Hello, Terraform!"
}
</code></pre>
<p>🧠 <strong>Explanation:</strong></p>
<ul>
<li><p><code>substr(text, 0, 5)</code> → starts from position 0 (the first letter), takes 5 letters.</p>
</li>
<li><p><code>replace()</code> → looks for <code>"World"</code> and swaps it with <code>"Terraform"</code>.</p>
</li>
</ul>
<hr />
<h3 id="heading-real-world-use-case">💡 Real-world Use Case</h3>
<p>You can use string formatting for naming multiple instances:</p>
<pre><code class="lang-plaintext">locals {
  environment = "dev"
  server_name = format("%s-%s-%03d", "myapp", local.environment, 1)
}
# Output: "myapp-dev-001"
</code></pre>
<p>This makes your resource names look clean and consistent like:</p>
<pre><code class="lang-plaintext">myapp-dev-001  
myapp-dev-002  
myapp-dev-003
</code></pre>
<hr />
<h2 id="heading-3-string-splitting-and-joining">🧩 3. <strong>String Splitting and Joining</strong></h2>
<p>You often need to <strong>break apart</strong> or <strong>combine</strong> strings in Terraform — for example, splitting an email or joining tags into a single line.</p>
<pre><code class="lang-plaintext">locals {
  words    = split(",", "apple,banana,orange")
  csv      = join(",", ["apple", "banana", "orange"])
  chomped  = chomp("hello\n")
  indented = indent(2, "hello\nworld")
}
</code></pre>
<h3 id="heading-explanation">Explanation:</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Description</td><td>Example</td><td>Output</td></tr>
</thead>
<tbody>
<tr>
<td><code>split(separator, string)</code></td><td>Breaks a string into a list</td><td><code>split(",", "a,b,c")</code></td><td><code>["a", "b", "c"]</code></td></tr>
<tr>
<td><code>join(separator, list)</code></td><td>Joins list into one string</td><td><code>join("-", ["a", "b", "c"])</code></td><td><code>"a-b-c"</code></td></tr>
<tr>
<td><code>chomp(string)</code></td><td>Removes a trailing newline (<code>\n</code>)</td><td><code>chomp("hello\n")</code></td><td><code>"hello"</code></td></tr>
<tr>
<td><code>indent(spaces, text)</code></td><td>Adds spaces to the beginning of each line</td><td><code>indent(2, "hi\nworld")</code></td><td><code>" hi\n world"</code></td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-real-world-example-extracting-email-domain">💡 Real-world Example: Extracting Email Domain</h3>
<pre><code class="lang-plaintext">locals {
  email  = "admin@example.com"
  domain = split("@", local.email)[1]
}
# Output: "admin", "example.com"
</code></pre>
<p>🧠 Explanation:<br /><code>split("@",</code> <a target="_blank" href="http://local.email"><code>local.email</code></a><code>)</code> → splits into <code>["admin", "</code><a target="_blank" href="http://example.com"><code>example.com</code></a><code>"]</code><br /><code>[1]</code> → gets the second element (index starts at 0)</p>
<hr />
<h2 id="heading-4-practical-string-example-in-terraform">🧱 4. <strong>Practical String Example in Terraform</strong></h2>
<p>Let’s put all the string functions together into a real Terraform example 👇</p>
<pre><code class="lang-plaintext">variable "environment" {
  default = "dev"
}

variable "project_name" {
  default = "MyApp"
}

locals {
  # 1️⃣ Make clean prefix (lowercase, environment)
  resource_prefix = "${lower(var.project_name)}-${var.environment}"
  # =&gt; "myapp-dev"

  # 2️⃣ Format instance names (like server IDs)
  instance_names = [
    for i in range(3) :
    format("%s-web-%03d", local.resource_prefix, i + 1)
  ]
  # =&gt; ["myapp-dev-web-001", "myapp-dev-web-002", "myapp-dev-web-003"]

  # 3️⃣ Extract domain name from admin email
  admin_email = "admin@example.com"
  domain      = split("@", local.admin_email)[1]
  # =&gt; "example.com"

  # 4️⃣ Build DNS name dynamically
  dns_name = "${local.resource_prefix}.${local.domain}"
  # =&gt; "myapp-dev.example.com"
}
</code></pre>
<h3 id="heading-explanation-1">🧠 Explanation:</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Local Name</td><td>Description</td><td>Output</td></tr>
</thead>
<tbody>
<tr>
<td><code>resource_prefix</code></td><td>Creates a clean project + environment prefix</td><td><code>"myapp-dev"</code></td></tr>
<tr>
<td><code>instance_names</code></td><td>Formats web server names dynamically</td><td>List of <code>"myapp-dev-web-00X"</code></td></tr>
<tr>
<td><code>domain</code></td><td>Extracts domain from email</td><td><code>"</code><a target="_blank" href="http://example.com"><code>example.com</code></a><code>"</code></td></tr>
<tr>
<td><code>dns_name</code></td><td>Combines prefix + domain</td><td><code>"</code><a target="_blank" href="http://myapp-dev.example.com"><code>myapp-dev.example.com</code></a><code>"</code></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-try-it-in-terraform-console">🧪 Try it in Terraform Console</h2>
<p>You can test any string function easily:</p>
<pre><code class="lang-plaintext">terraform console
&gt; lower("Terraform")
&gt; upper("cloud")
&gt; split(",", "a,b,c")
&gt; join("-", ["apple", "banana"])
&gt; format("%s-%03d", "web", 7)
&gt; replace("dev", "d", "prod")
&gt; exit
</code></pre>
<hr />
<h2 id="heading-summary-table-1">🧭 Summary Table</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Description</td><td>Example</td><td>Output</td></tr>
</thead>
<tbody>
<tr>
<td><code>lower()</code></td><td>Lowercase all letters</td><td><code>"HELLO"</code> → <code>"hello"</code></td><td></td></tr>
<tr>
<td><code>upper()</code></td><td>Uppercase all letters</td><td><code>"hello"</code> → <code>"HELLO"</code></td><td></td></tr>
<tr>
<td><code>title()</code></td><td>Capitalize words</td><td><code>"hi world"</code> → <code>"Hi World"</code></td><td></td></tr>
<tr>
<td><code>trimspace()</code></td><td>Remove spaces around text</td><td><code>" hi "</code> → <code>"hi"</code></td><td></td></tr>
<tr>
<td><code>replace()</code></td><td>Replace text</td><td><code>"hi world"</code> → <code>"hi Terraform"</code></td><td></td></tr>
<tr>
<td><code>split()</code></td><td>Split into list</td><td><code>"a,b,c"</code> → <code>["a","b","c"]</code></td><td></td></tr>
<tr>
<td><code>join()</code></td><td>Join list into string</td><td><code>["a","b","c"]</code> → <code>"a-b-c"</code></td><td></td></tr>
<tr>
<td><code>format()</code></td><td>Build strings with patterns</td><td><code>"web-%03d"</code> + 5 → <code>"web-005"</code></td><td></td></tr>
<tr>
<td><code>startswith()</code></td><td>Checks beginning of text</td><td><code>"Terraform"</code> → <code>true</code></td><td></td></tr>
<tr>
<td><code>endswith()</code></td><td>Checks end of text</td><td><code>"Terraform"</code> → <code>true</code></td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-hands-on-lab">🧪 Hands-On Lab</h3>
<h2 id="heading-project-structure">📁 Project Structure</h2>
<pre><code class="lang-plaintext">terraform-numeric-string-lab/
├── main.tf
├── variables.tf
├── outputs.tf
└── locals.tf
</code></pre>
<hr />
<h2 id="heading-step-1-variablestfhttpvariablestf">🧰 Step 1: <a target="_blank" href="http://variables.tf">variables.tf</a></h2>
<p>This file defines user inputs.<br />We’ll use variables for project naming, instance count, and region.</p>
<pre><code class="lang-plaintext"># variables.tf

variable "aws_region" {
  description = "AWS region where EC2 instances will be created"
  type        = string
  default     = "us-east-1"
}

variable "project_name" {
  description = "Name of your project"
  type        = string
  default     = "NumericStringDemo"
}

variable "environment" {
  description = "Deployment environment (dev, stage, prod)"
  type        = string
  default     = "dev"
}

variable "instance_count" {
  description = "Number of instances you want to create"
  type        = number
  default     = 3
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "base_storage" {
  description = "Base storage size (GB) for each instance"
  type        = number
  default     = 10
}
</code></pre>
<hr />
<h2 id="heading-step-2-localstfhttplocalstf">🧮 Step 2: <a target="_blank" href="http://locals.tf">locals.tf</a></h2>
<p>Here’s where we use <strong>numeric and string functions</strong> to calculate and generate smart values.</p>
<pre><code class="lang-plaintext"># locals.tf

locals {
  # ✅ String Functions

  # Create a consistent lowercase prefix for all resources
  resource_prefix = "${lower(var.project_name)}-${lower(var.environment)}"
  # Example: "numericstringdemo-dev"

  # Format EC2 instance names dynamically
  instance_names = [
    for i in range(var.instance_count) :
    format("%s-web-%03d", local.resource_prefix, i + 1)
  ]
  # ["numericstringdemo-dev-web-001", "numericstringdemo-dev-web-002", ...]

  # ✅ Numeric Functions

  # Ensure we have at least 1 instance
  final_instance_count = max(1, var.instance_count)

  # Calculate total storage: power of 2 * base storage
  # e.g., if count=3 =&gt; ceil(log(3,2))=2 =&gt; pow(2,2)=4 =&gt; 4 * 10 = 40GB total
  total_storage_gb = pow(2, ceil(log(local.final_instance_count, 2))) * var.base_storage

  # Divide instances into subnets (2 per subnet, rounded up)
  subnet_count = ceil(local.final_instance_count / 2.0)

  # String join example: create a simple description
  description = join(" ", [
    "Project:", var.project_name,
    "Environment:", var.environment,
    "Instances:", tostring(local.final_instance_count)
  ])
}
</code></pre>
<hr />
<h2 id="heading-step-3-maintfhttpmaintf">☁️ Step 3: <a target="_blank" href="http://main.tf">main.tf</a></h2>
<p>Now, we’ll use those <code>locals</code> to dynamically create EC2 instances with <strong>smart naming and scaling logic</strong>.</p>
<pre><code class="lang-plaintext"># main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# ✅ Data source to fetch latest Amazon Linux AMI
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# ✅ Create EC2 instances using for_each
resource "aws_instance" "web" {
  for_each = toset(local.instance_names)

  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type

  tags = {
    Name        = each.key
    Environment = var.environment
    Description = local.description
  }
}
</code></pre>
<h3 id="heading-explanation-2">🧠 Explanation:</h3>
<ul>
<li><p><code>for_each = toset(local.instance_names)</code> — creates one instance per formatted name</p>
</li>
<li><p><code>each.key</code> — gives each instance name dynamically</p>
</li>
<li><p><code>tags</code> use string functions to create readable tags</p>
</li>
<li><p>Numeric functions were already used in <a target="_blank" href="http://locals.tf"><code>locals.tf</code></a> to decide <strong>count</strong>, <strong>storage</strong>, and <strong>subnet count</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-step-4-outputstfhttpoutputstf">📤 Step 4: <a target="_blank" href="http://outputs.tf">outputs.tf</a></h2>
<p>We’ll show computed results and demonstrate how our functions affected the setup.</p>
<pre><code class="lang-plaintext"># outputs.tf

output "instance_names" {
  description = "List of generated EC2 instance names"
  value       = local.instance_names
}

output "total_storage_gb" {
  description = "Total calculated storage based on instance count"
  value       = local.total_storage_gb
}

output "subnet_count" {
  description = "Calculated subnet count (2 instances per subnet)"
  value       = local.subnet_count
}

output "description" {
  description = "Auto-generated description using join and tostring"
  value       = local.description
}
</code></pre>
<hr />
<h2 id="heading-step-5-run-and-test">🧩 Step 5: Run and Test</h2>
<h3 id="heading-initialize-terraform">✅ Initialize Terraform</h3>
<pre><code class="lang-plaintext">terraform init
</code></pre>
<h3 id="heading-validate-your-code">✅ Validate your code</h3>
<pre><code class="lang-plaintext">terraform validate
</code></pre>
<h3 id="heading-preview-your-plan">✅ Preview your plan</h3>
<pre><code class="lang-plaintext">terraform plan
</code></pre>
<p>You’ll see something like</p>
<pre><code class="lang-plaintext">Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + instance_names = [
      "numericstringdemo-dev-web-001",
      "numericstringdemo-dev-web-002",
      "numericstringdemo-dev-web-003",
    ]
  + total_storage_gb = 40
  + subnet_count = 2
  + description = "Project: NumericStringDemo Environment: dev Instances: 3"
</code></pre>
<h3 id="heading-deploy-your-lab">✅ Deploy your lab</h3>
<pre><code class="lang-plaintext">terraform apply
</code></pre>
<h3 id="heading-check-outputs">✅ Check outputs</h3>
<pre><code class="lang-plaintext">terraform output
</code></pre>
<p>Expected output:</p>
<pre><code class="lang-plaintext">instance_names = [
  "numericstringdemo-dev-web-001",
  "numericstringdemo-dev-web-002",
  "numericstringdemo-dev-web-003",
]
total_storage_gb = 40
subnet_count = 2
description = "Project: NumericStringDemo Environment: dev Instances: 3"
</code></pre>
<h3 id="heading-clean-up">✅ Clean up</h3>
<pre><code class="lang-plaintext">terraform destroy -auto-approve
</code></pre>
<hr />
<h2 id="heading-what-you-learned">🧠 What You Learned</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Concept</td><td>Function Used</td><td>Purpose</td></tr>
</thead>
<tbody>
<tr>
<td>Ensure minimum instance count</td><td><code>max()</code></td><td>Prevents 0 or negative numbers</td></tr>
<tr>
<td>Dynamic scaling formula</td><td><code>log()</code>, <code>ceil()</code>, <code>pow()</code></td><td>Calculates total storage size</td></tr>
<tr>
<td>Group instances per subnet</td><td><code>ceil()</code></td><td>Rounds up subnet numbers</td></tr>
<tr>
<td>Lowercase resource prefix</td><td><code>lower()</code></td><td>Consistent resource naming</td></tr>
<tr>
<td>Format instance names</td><td><code>format()</code></td><td>Generates <code>web-001</code>, <code>web-002</code> style</td></tr>
<tr>
<td>Combine words into readable text</td><td><code>join()</code></td><td>Builds description text</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 16: Dynamic Blocks for Flexible Resources</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Collection functions (merge, lookup, flatten)</p>
</li>
<li><p>Encoding functions (base64, json, yaml)</p>
</li>
<li><p>Crypto functions (md5, sha256, bcrypt)</p>
</li>
<li><p>Date/time functions (timestamp, formatdate)</p>
</li>
<li><p>Network functions (cidrsubnet, cidrhost)</p>
</li>
<li><p>Filesystem functions (file, templatefile)</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-13-conditional-expressions-and-logic">← Day 13: Conditionals &amp; Logic</a> | <a target="_blank" href="day16.md">Day 15: Dynamic Blocks →</a></p>
<hr />
<p><em>Remember: Functions are powerful tools for data transformation and manipulation!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 13: Conditional Expressions & Logic]]></title><description><![CDATA[Welcome to Day 13 - Today’s topic is a big one — Conditional Logic in Terraform.By the end of this lesson, you’ll be able to make your Terraform configurations smart, flexible, and environment-aware—meaning your infrastructure can automatically adjus...]]></description><link>https://stackopsdiary.site/day-13-conditional-expressions-and-logic</link><guid isPermaLink="true">https://stackopsdiary.site/day-13-conditional-expressions-and-logic</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Mon, 20 Oct 2025 12:30:13 GMT</pubDate><content:encoded><![CDATA[<p>Welcome to Day 13 - Today’s topic is a big one — <strong>Conditional Logic in Terraform</strong>.<br />By the end of this lesson, you’ll be able to make your Terraform configurations <em>smart</em>, <em>flexible</em>, and <em>environment-aware</em>—meaning your infrastructure can automatically adjust its setup for <strong>dev</strong>, <strong>staging</strong>, or <strong>production</strong> environments.</p>
<p>Think of it like this:</p>
<blockquote>
<p>Instead of you manually changing settings for each environment, Terraform can make decisions for you automatically using <strong>conditions</strong>.</p>
</blockquote>
<hr />
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<p>Here’s what you’ll learn step by step:</p>
<ul>
<li><p>✅ Understand <strong>conditional expressions (ternary operator)</strong></p>
</li>
<li><p>✅ Learn <strong>logical operators (AND, OR, NOT)</strong></p>
</li>
<li><p>✅ Write <strong>complex conditional logic</strong></p>
</li>
<li><p>✅ Use <strong>conditions to create or skip resources</strong></p>
</li>
<li><p>✅ Build <strong>environment-aware infrastructure</strong></p>
</li>
</ul>
<p>Let’s dive in! 🚀</p>
<hr />
<h2 id="heading-conditional-expressions">🔀 Conditional Expressions</h2>
<p>A <strong>conditional expression</strong> in Terraform works just like an <strong>if-else</strong> statement in programming.</p>
<p><strong>Basic syntax:</strong></p>
<pre><code class="lang-plaintext">condition ? true_value : false_value
</code></pre>
<p>It means:</p>
<blockquote>
<p>“If the condition is true, use the first value; otherwise, use the second.”</p>
</blockquote>
<p>Let’s see this in practice:</p>
<h3 id="heading-example">Example</h3>
<pre><code class="lang-plaintext">variable "environment" {
  type = string
}

locals {
  # If the environment is prod, use a larger instance
  instance_type = var.environment == "prod" ? "t3.large" : "t2.small"

  # If prod, enable monitoring; otherwise disable it
  monitoring = var.environment == "prod" ? true : false

  # Nested conditions for different environments
  instance_count = (
    var.environment == "prod" ? 5 :
    var.environment == "staging" ? 3 :
    1  # default for dev
  )
}
</code></pre>
<p>💡 <strong>Explanation:</strong></p>
<ul>
<li><p>The <code>?</code> and <code>:</code> symbols work like <em>if ... else</em> logic.</p>
</li>
<li><p>Terraform checks your environment (like <code>prod</code>, <code>staging</code>, or <code>dev</code>) and decides values automatically.</p>
</li>
<li><p>You can even <strong>nest</strong> multiple conditions for more flexibility.</p>
</li>
</ul>
<hr />
<h2 id="heading-conditional-values">Conditional Values</h2>
<p>Terraform conditionals can be used with <strong>booleans</strong>, <strong>numbers</strong>, <strong>strings</strong>, and <strong>lists</strong>.</p>
<h3 id="heading-example-with-booleans">✅ Example with booleans</h3>
<pre><code class="lang-plaintext">locals {
  is_production   = var.environment == "prod"
  backup_enabled  = local.is_production ? true : false
}
</code></pre>
<p><strong>Explanation:</strong><br />If <code>environment</code> is <code>prod</code>, <code>backup_enabled</code> becomes <code>true</code>. Otherwise, it’s <code>false</code>.</p>
<hr />
<h3 id="heading-example-with-numbers">🔢 Example with numbers</h3>
<pre><code class="lang-plaintext">locals {
  min_instances = var.high_availability ? 3 : 1
  max_instances = var.high_availability ? 10 : 3
}
</code></pre>
<p>If high availability is enabled, Terraform will deploy more instances automatically.</p>
<hr />
<h3 id="heading-example-with-strings">🧵 Example with strings</h3>
<pre><code class="lang-plaintext">locals {
  db_instance_class = var.environment == "prod" ? "db.r5.xlarge" : "db.t3.small"
  dns_zone_name     = var.environment == "prod" ? "example.com" : "${var.environment}.example.com"
}
</code></pre>
<p>So in <code>staging</code>, your DNS could become <a target="_blank" href="http://staging.example.com"><code>staging.example.com</code></a>.</p>
<hr />
<h3 id="heading-example-with-lists">📋 Example with lists</h3>
<pre><code class="lang-plaintext">locals {
  availability_zones = var.multi_az ? ["us-east-1a", "us-east-1b", "us-east-1c"] : ["us-east-1a"]
}
</code></pre>
<p>Terraform chooses how many availability zones to use based on <code>multi_az</code>.</p>
<hr />
<h2 id="heading-comparison-operators">🔢 Comparison Operators</h2>
<p>Comparison operators let you compare values just like in regular programming.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Operator</td><td>Meaning</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>==</code></td><td>Equal to</td><td><code>var.env == "prod"</code></td></tr>
<tr>
<td><code>!=</code></td><td>Not equal</td><td><code>var.env != "dev"</code></td></tr>
<tr>
<td><code>&gt;</code></td><td>Greater than</td><td><code>var.count &gt; 5</code></td></tr>
<tr>
<td><code>&gt;=</code></td><td>Greater than or equal</td><td><code>var.count &gt;= 5</code></td></tr>
<tr>
<td><code>&lt;</code></td><td>Less than</td><td><code>var.count &lt; 5</code></td></tr>
<tr>
<td><code>&lt;=</code></td><td>Less than or equal</td><td><code>var.count &lt;= 5</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-example-1">Example:</h3>
<pre><code class="lang-plaintext">locals {
  needs_scaling = var.user_count &gt; 1000 ? true : false
  is_free_tier  = var.instance_count &lt;= 1 ? true : false
  is_valid_port = var.port &gt;= 1 &amp;&amp; var.port &lt;= 65535 ? true : false
}
</code></pre>
<p>Terraform can check if a number is within a valid range or if it meets a threshold.</p>
<hr />
<h2 id="heading-logical-operators">🔗 Logical Operators</h2>
<p>Terraform supports three main logical operators — <strong>AND</strong>, <strong>OR</strong>, and <strong>NOT</strong>.</p>
<hr />
<h3 id="heading-and-operator-ampamp">AND Operator (<code>&amp;&amp;</code>)</h3>
<p>This means <em>both conditions must be true</em>.</p>
<pre><code class="lang-plaintext">locals {
  enable_backup = var.environment == "prod" &amp;&amp; var.disk_size &gt; 100
  create_lb     = var.environment == "prod" &amp;&amp; var.instance_count &gt; 1
}
</code></pre>
<p>✅ Terraform enables backups <em>only if</em> it’s production <strong>and</strong> disk size is greater than 100 GB.</p>
<hr />
<h3 id="heading-or-operator">OR Operator (<code>||</code>)</h3>
<p>This means <em>at least one condition must be true</em>.</p>
<pre><code class="lang-plaintext">locals {
  monitoring = var.environment == "prod" || var.environment == "staging"
  create_nat = var.environment == "prod" || var.high_availability == true
}
</code></pre>
<p>✅ Monitoring is enabled for both prod <strong>and</strong> staging environments.</p>
<hr />
<h3 id="heading-not-operator">NOT Operator (<code>!</code>)</h3>
<p>This <strong>reverses</strong> the boolean value.</p>
<pre><code class="lang-plaintext">locals {
  is_development = !local.is_production
  skip_backup = !(var.environment == "prod")
}
</code></pre>
<p>✅ If the environment is <em>not</em> production, Terraform skips creating backups.</p>
<hr />
<h2 id="heading-combining-operators">Combining Operators</h2>
<p>You can mix and match these operators to create advanced conditions.</p>
<pre><code class="lang-plaintext">locals {
  create_multi_az_db = (
    (var.environment == "prod" || var.environment == "staging") &amp;&amp;
    var.multi_az == true
  )
}
</code></pre>
<p>✅ This creates a multi-AZ database only if the environment is <strong>prod or staging</strong> <em>and</em> multi-AZ is enabled.</p>
<hr />
<h2 id="heading-conditional-resource-creation">🎭 Conditional Resource Creation</h2>
<p>One of the most powerful uses of conditionals is deciding <strong>whether Terraform should create a resource</strong>.</p>
<hr />
<h3 id="heading-example-1-create-or-skip-a-resource">Example 1: Create or Skip a Resource</h3>
<pre><code class="lang-plaintext">resource "aws_instance" "bastion" {
  count = var.environment == "prod" ? 1 : 0
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
}
</code></pre>
<p>✅ Terraform creates a bastion host only in production.<br />If it’s dev or staging, it skips it entirely!</p>
<hr />
<h3 id="heading-example-2-conditional-resource-attributes">Example 2: Conditional Resource Attributes</h3>
<pre><code class="lang-plaintext">resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.environment == "prod" ? "t3.large" : "t2.micro"

  monitoring = var.environment == "prod" ? true : false
  subnet_id  = var.environment == "prod" ? aws_subnet.private[0].id : aws_subnet.public[0].id
}
</code></pre>
<p>✅ The same resource can adapt its configuration depending on the environment.</p>
<hr />
<h3 id="heading-example-3-conditional-blocks-with-dynamic">Example 3: Conditional Blocks with <code>dynamic</code></h3>
<p>Sometimes you need to include or exclude entire blocks, such as security rules.</p>
<pre><code class="lang-plaintext">resource "aws_security_group" "web" {
  dynamic "ingress" {
    for_each = var.environment == "dev" ? [1] : []
    content {
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}
</code></pre>
<p>✅ SSH access is only added in the dev environment.</p>
<hr />
<h2 id="heading-advanced-conditional-patterns">🧮 Advanced Conditional Patterns</h2>
<p>Terraform also provides functions like <code>contains()</code> and <code>lookup()</code> to simplify logic.</p>
<h3 id="heading-multi-way-conditions">Multi-Way Conditions</h3>
<p>You can chain conditions for multiple environments:</p>
<pre><code class="lang-plaintext">locals {
  instance_type = (
    var.environment == "prod" ? "t3.2xlarge" :
    var.environment == "staging" ? "t3.large" :
    var.environment == "dev" ? "t2.micro" :
    "t2.nano"
  )
}
</code></pre>
<hr />
<h3 id="heading-conditional-with-contains">Conditional with <code>contains()</code></h3>
<pre><code class="lang-plaintext">locals {
  is_valid_env   = contains(["dev", "staging", "prod"], var.environment)
  enable_feature = contains(["prod", "staging"], var.environment) ? true : false
}
</code></pre>
<p>✅ Checks if a value exists inside a list.</p>
<hr />
<h3 id="heading-conditional-with-lookup">Conditional with <code>lookup()</code></h3>
<pre><code class="lang-plaintext">variable "instance_type_map" {
  type = map(string)
  default = {
    dev     = "t2.micro"
    staging = "t2.small"
    prod    = "t3.large"
  }
}

locals {
  instance_type = lookup(var.instance_type_map, var.environment, "t2.nano")
}
</code></pre>
<p>✅ If your environment isn’t found in the map, Terraform uses the fallback (<code>t2.nano</code>).</p>
<hr />
<h3 id="heading-null-conditionals">Null Conditionals</h3>
<pre><code class="lang-plaintext">variable "custom_ami" {
  type    = string
  default = null
}

resource "aws_instance" "web" {
  ami = var.custom_ami != null ? var.custom_ami : data.aws_ami.amazon_linux.id
}
</code></pre>
<p>✅ If you don’t specify a custom AMI, Terraform will use the default Amazon Linux image.</p>
<h2 id="heading-hands-on-lab-conditional-infrastructure">🧪 Hands-On Lab: Conditional Infrastructure</h2>
<p>Let’s build an environment-aware infrastructure!</p>
<h3 id="heading-step-1-create-a-new-project-folder">🧱 Step 1: Create a New Project Folder</h3>
<p>Open your terminal and create a new folder for this lab:</p>
<pre><code class="lang-plaintext">mkdir terraform-conditionals-lab
cd terraform-conditionals-lab
</code></pre>
<p>This will be your working directory for today’s exercise.</p>
<hr />
<h3 id="heading-step-2-create-variablestfhttpvariablestf">🧾 Step 2: Create <a target="_blank" href="http://variables.tf"><code>variables.tf</code></a></h3>
<p>This file defines all the inputs your Terraform configuration will use.<br />You’ll define variables like environment, monitoring, backup, etc.</p>
<pre><code class="lang-plaintext"># variables.tf

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "project_name" {
  type    = string
  default = "conditional-demo"
}

variable "environment" {
  description = "Environment (dev, staging, prod)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "enable_monitoring" {
  description = "Override monitoring setting"
  type        = bool
  default     = null
}

variable "enable_backup" {
  description = "Override backup setting"
  type        = bool
  default     = null
}

variable "multi_az" {
  description = "Deploy across multiple availability zones"
  type        = bool
  default     = false
}

variable "high_availability" {
  description = "Enable high availability features"
  type        = bool
  default     = false
}

variable "compliance_mode" {
  description = "Enable compliance features (encryption, logging, etc.)"
  type        = bool
  default     = false
}
</code></pre>
<p>🧠 <strong>Explanation:</strong></p>
<ul>
<li><p>These variables make your configuration flexible.</p>
</li>
<li><p><code>environment</code> can only be one of <code>dev</code>, <code>staging</code>, or <code>prod</code>.</p>
</li>
<li><p>The <code>null</code> defaults for <code>enable_backup</code> and <code>enable_monitoring</code> let Terraform decide automatically based on the environment.</p>
</li>
</ul>
<hr />
<h3 id="heading-step-3-create-localstfhttplocalstf">🧮 Step 3: Create <a target="_blank" href="http://locals.tf"><code>locals.tf</code></a></h3>
<p>This is where the <strong>conditional logic</strong> lives.<br />You’ll use locals to calculate derived values from variables (like how many instances to create or which subnet to use).</p>
<pre><code class="lang-plaintext"># locals.tf

locals {
  # Environment checks
  is_production = var.environment == "prod"
  is_staging    = var.environment == "staging"
  is_dev        = var.environment == "dev"

  # Auto-enable features
  monitoring_enabled = var.enable_monitoring != null ? var.enable_monitoring : local.is_production || local.is_staging
  backup_enabled     = var.enable_backup != null ? var.enable_backup : local.is_production

  # Instance configuration
  instance_config = {
    dev = {
      type  = "t2.micro"
      count = 1
    }
    staging = {
      type  = "t2.small"
      count = 2
    }
    prod = {
      type  = var.high_availability ? "t3.large" : "t3.medium"
      count = var.high_availability ? 3 : 2
    }
  }

  instance_type  = local.instance_config[var.environment].type
  instance_count = local.instance_config[var.environment].count

  # Network configuration
  create_nat_gateway = local.is_production || var.multi_az
  az_count          = var.multi_az ? 3 : (local.is_production ? 2 : 1)

  # Security and compliance
  enable_encryption = local.is_production || var.compliance_mode
  enable_flow_logs  = local.is_production || var.compliance_mode

  allowed_ssh_cidrs = local.is_dev ? ["0.0.0.0/0"] : ["10.0.0.0/8"]

  # Backup retention period
  backup_retention_days = (
    local.is_production ? 30 :
    local.is_staging ? 14 :
    7
  )

  # Common tags
  common_tags = merge(
    {
      Project     = var.project_name
      Environment = var.environment
      ManagedBy   = "Terraform"
    },
    local.is_production ? { Backup = "Required", Monitoring = "Enhanced" } : {},
    var.compliance_mode ? { Compliance = "Enabled" } : {}
  )

  # Feature flags summary
  features = {
    monitoring        = local.monitoring_enabled
    backup            = local.backup_enabled
    nat_gateway       = local.create_nat_gateway
    multi_az          = var.multi_az
    encryption        = local.enable_encryption
    flow_logs         = local.enable_flow_logs
    high_availability = var.high_availability
  }
}
</code></pre>
<p>💡 <strong>Explanation:</strong></p>
<ul>
<li><p>Terraform now knows which settings apply to each environment automatically.</p>
</li>
<li><p>For example:</p>
<ul>
<li><p>In <strong>dev</strong>, only 1 small instance, no NAT, no backup.</p>
</li>
<li><p>In <strong>prod</strong>, multiple instances, NAT gateways, encryption, and longer backups.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-step-4-create-maintfhttpmaintf">☁️ Step 4: Create <a target="_blank" href="http://main.tf"><code>main.tf</code></a></h3>
<p>This file defines your actual AWS resources using the conditionals you built.</p>
<pre><code class="lang-plaintext"># main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = local.common_tags
  }
}

data "aws_availability_zones" "available" {
  state = "available"
}

# 1️⃣ VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = { Name = "${var.project_name}-${var.environment}-vpc" }
}

# 2️⃣ Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = { Name = "${var.project_name}-${var.environment}-igw" }
}

# 3️⃣ Public Subnets
resource "aws_subnet" "public" {
  count = local.az_count
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-${var.environment}-public-${count.index + 1}"
    Type = "Public"
  }
}

# 4️⃣ Private Subnets (only for prod or multi-AZ)
resource "aws_subnet" "private" {
  count = local.is_production || var.multi_az ? local.az_count : 0
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 10}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "${var.project_name}-${var.environment}-private-${count.index + 1}"
    Type = "Private"
  }
}

# NAT Gateway (conditional)
resource "aws_nat_gateway" "main" {
  count = local.create_nat_gateway ? local.az_count : 0

  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  tags = {
    Name = "${var.project_name}-${var.environment}-nat-${count.index + 1}"
  }
}

# Security Group with conditional rules
resource "aws_security_group" "web" {
  name        = "${var.project_name}-${var.environment}-web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id

  # HTTP - always allowed
  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # HTTPS - always allowed
  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # SSH - conditional based on environment
  dynamic "ingress" {
    for_each = local.is_dev || local.is_staging ? [1] : []
    content {
      description = "SSH (Dev/Staging only)"
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
      cidr_blocks = local.allowed_ssh_cidrs
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-${var.environment}-web-sg"
  }
}

# AMI Data Source
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# EC2 Instances
resource "aws_instance" "web" {
  count = local.instance_count

  ami           = data.aws_ami.amazon_linux.id
  instance_type = local.instance_type

  # Use private subnet in prod with NAT, public in dev/staging
  subnet_id = (local.is_production &amp;&amp; local.create_nat_gateway) ? aws_subnet.private[count.index % length(aws_subnet.private)].id : aws_subnet.public[count.index % length(aws_subnet.public)].id

  vpc_security_group_ids = [aws_security_group.web.id]

  # Conditional monitoring
  monitoring = local.monitoring_enabled

  # Conditional EBS encryption
  root_block_device {
    encrypted   = local.enable_encryption
    volume_type = local.is_production ? "gp3" : "gp2"
    volume_size = local.is_production ? 30 : 20
  }

  user_data = &lt;&lt;-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "&lt;h1&gt;${var.environment} - Instance ${count.index + 1}&lt;/h1&gt;" &gt; /var/www/html/index.html
              echo "&lt;p&gt;Type: ${local.instance_type}&lt;/p&gt;" &gt;&gt; /var/www/html/index.html
              echo "&lt;p&gt;Monitoring: ${local.monitoring_enabled}&lt;/p&gt;" &gt;&gt; /var/www/html/index.html
              EOF

  tags = {
    Name  = "${var.project_name}-${var.environment}-web-${count.index + 1}"
    Tier  = "Web"
    Index = count.index + 1
  }
}

# VPC Flow Logs (conditional)
resource "aws_flow_log" "main" {
  count = local.enable_flow_logs ? 1 : 0

  iam_role_arn    = aws_iam_role.flow_logs[0].arn
  log_destination = aws_cloudwatch_log_group.flow_logs[0].arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.main.id

  tags = {
    Name = "${var.project_name}-${var.environment}-flow-logs"
  }
}

# CloudWatch Log Group for Flow Logs (conditional)
resource "aws_cloudwatch_log_group" "flow_logs" {
  count = local.enable_flow_logs ? 1 : 0

  name              = "/aws/vpc/${var.project_name}-${var.environment}"
  retention_in_days = local.backup_retention_days

  tags = {
    Name = "${var.project_name}-${var.environment}-flow-logs"
  }
}

# IAM Role for Flow Logs (conditional)
resource "aws_iam_role" "flow_logs" {
  count = local.enable_flow_logs ? 1 : 0

  name = "${var.project_name}-${var.environment}-flow-logs-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "vpc-flow-logs.amazonaws.com"
      }
    }]
  })
}

resource "aws_iam_role_policy" "flow_logs" {
  count = local.enable_flow_logs ? 1 : 0

  name = "flow-logs-policy"
  role = aws_iam_role.flow_logs[0].id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams"
      ]
      Effect   = "Allow"
      Resource = "*"
    }]
  })
}
</code></pre>
<p>💡 <strong>Explanation:</strong></p>
<ul>
<li><p>Terraform uses the local values to decide <strong>how many</strong> of each resource to create.</p>
</li>
<li><p>For example, NAT gateways only exist in <code>prod</code> or when <code>multi_az</code> is true.</p>
</li>
<li><p>SSH rules are added only in <code>dev</code> or <code>staging</code> for safety.</p>
</li>
</ul>
<hr />
<h3 id="heading-step-5-create-outputstfhttpoutputstf">📤 Step 5: Create <a target="_blank" href="http://outputs.tf"><code>outputs.tf</code></a></h3>
<p>Finally, you’ll add output blocks to display summary information after <code>terraform apply</code>.</p>
<pre><code class="lang-plaintext"># outputs.tf

output "environment_info" {
  description = "Environment configuration summary"
  value = {
    environment       = var.environment
    is_production     = local.is_production
    is_staging        = local.is_staging
    is_dev            = local.is_dev
    features_enabled  = local.features
  }
}

output "infrastructure_details" {
  description = "Infrastructure configuration"
  value = {
    instance_type       = local.instance_type
    instance_count      = local.instance_count
    availability_zones  = local.az_count
    monitoring          = local.monitoring_enabled
    backup_enabled      = local.backup_enabled
    backup_retention    = local.backup_retention_days
    nat_gateway_count   = local.create_nat_gateway ? local.az_count : 0
  }
}
</code></pre>
<p>When you run:</p>
<pre><code class="lang-plaintext">terraform apply -var="environment=staging"
</code></pre>
<p>Terraform will automatically:</p>
<ul>
<li><p>Use smaller instances</p>
</li>
<li><p>Skip private subnets</p>
</li>
<li><p>Enable monitoring</p>
</li>
<li><p>Set backup retention to 14 days<br />  All <strong>without changing a single line of code</strong>.</p>
</li>
</ul>
<hr />
<h3 id="heading-summary-of-the-lab">🧠 Summary of the Lab</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Environment</td><td>Instance Type</td><td>NAT Gateway</td><td>Backup</td><td>Monitoring</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Dev</strong></td><td><code>t2.micro</code></td><td>❌</td><td>❌</td><td>❌</td></tr>
<tr>
<td><strong>Staging</strong></td><td><code>t2.small</code></td><td>❌</td><td>✅</td><td>✅</td></tr>
<tr>
<td><strong>Prod</strong></td><td><code>t3.medium/large</code></td><td>✅</td><td>✅</td><td>✅</td></tr>
</tbody>
</table>
</div><hr />
<p>✅ <strong>You’ve just built a dynamic, environment-aware Terraform project!</strong></p>
<p>Now you can:</p>
<ul>
<li><p>Use the same code for dev, staging, and prod</p>
</li>
<li><p>Let Terraform make smart choices based on variables</p>
</li>
<li><p>Simplify your infrastructure management</p>
</li>
</ul>
<h3 id="heading-step-6-test-different-environments"><strong>Step 6: Test Different Environments</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init
<span class="hljs-comment"># Test Development</span>
terraform plan -var=<span class="hljs-string">"environment=dev"</span>
<span class="hljs-comment"># Test Staging</span>
terraform plan -var=<span class="hljs-string">"environment=staging"</span>
<span class="hljs-comment"># Test Production</span>
terraform plan -var=<span class="hljs-string">"environment=prod"</span>
<span class="hljs-comment"># Test Production with High Availability</span>
terraform plan -var=<span class="hljs-string">"environment=prod"</span> -var=<span class="hljs-string">"high_availability=true"</span>
<span class="hljs-comment"># Apply development</span>
terraform apply -var=<span class="hljs-string">"environment=dev"</span> -auto-approve
<span class="hljs-comment"># View outputs</span>
terraform output
<span class="hljs-comment"># Clean up</span>
terraform destroy -var=<span class="hljs-string">"environment=dev"</span> -auto-approve
</code></pre>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Conditional expressions (ternary operator)</p>
</li>
<li><p>✅ Comparison and logical operators</p>
</li>
<li><p>✅ Combining conditions with AND, OR, NOT</p>
</li>
<li><p>✅ Conditional resource creation</p>
</li>
<li><p>✅ Multi-way conditionals</p>
</li>
<li><p>✅ Building environment-aware infrastructure</p>
</li>
</ul>
<h2 id="heading-week-2-complete">🎉 Week 2 Complete!</h2>
<p>Congratulations on completing Week 2! You’ve learned:</p>
<ul>
<li><p>Terraform CLI mastery</p>
</li>
<li><p>Advanced variable types and validation</p>
</li>
<li><p>Output and local values</p>
</li>
<li><p>Collection manipulation</p>
</li>
<li><p>Count and for_each</p>
</li>
<li><p>Conditional logic</p>
</li>
</ul>
<h2 id="heading-week-3-preview">🚀 Week 3 Preview</h2>
<p><strong>Day 15: Built-in Functions in Terraform</strong></p>
<p>Next week we’ll:</p>
<ul>
<li><p>Explore Terraform’s built-in functions</p>
</li>
<li><p>Learn dynamic blocks</p>
</li>
<li><p>Create reusable modules</p>
</li>
<li><p>Work with module sources</p>
</li>
<li><p>Configure remote state</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-12-count-and-foreach-creating-multiple-resources">← Day 12: Count &amp; For_Each</a> | <a target="_blank" href="day15.md">Day 15: Built-in Functions →</a></p>
<hr />
<p><em>Remember: Conditionals make your infrastructure smart and adaptable!</em></p>
<p><strong>🎊 Enjoy your Sunday! See you Monday for Week 3!</strong></p>
]]></content:encoded></item><item><title><![CDATA[Day 12: Count and For_Each - Creating Multiple Resources]]></title><description><![CDATA[Welcome to Day 12! Today we’ll master two powerful meta-arguments: count and for_each. These allow you to create multiple similar resources dynamically, making your infrastructure scalable and maintainable.
🎯 Today’s Goals

Understand count and for_...]]></description><link>https://stackopsdiary.site/day-12-count-and-foreach-creating-multiple-resources</link><guid isPermaLink="true">https://stackopsdiary.site/day-12-count-and-foreach-creating-multiple-resources</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Sat, 18 Oct 2025 12:30:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760786339784/021ab51f-e9ba-4957-91d7-6b8889a14ff2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 12! Today we’ll master two powerful meta-arguments: <strong>count</strong> and <strong>for_each</strong>. These allow you to create multiple similar resources dynamically, making your infrastructure scalable and maintainable.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Understand count and for_each meta-arguments</p>
</li>
<li><p>Learn when to use count vs for_each</p>
</li>
<li><p>Master resource indexing and references</p>
</li>
<li><p>Handle dynamic resource creation</p>
</li>
<li><p>Avoid common pitfalls</p>
</li>
</ul>
<h2 id="heading-the-count-meta-argument">🔢 The Count Meta-Argument</h2>
<p><strong>Count</strong> creates multiple instances of a resource based on a number.</p>
<h3 id="heading-basic-count-syntax"><strong>Basic Count Syntax</strong></h3>
<pre><code class="lang-plaintext">resource "aws_instance" "web" {
  count = 3

  ami           = "ami-12345"
  instance_type = "t2.micro"

  tags = {
    Name = "web-server-${count.index}"
  }
}

# Creates: web[0], web[1], web[2]
</code></pre>
<h3 id="heading-countindex"><strong>count.index</strong></h3>
<p><code>count.index</code> is the current iteration number (0-based):</p>
<pre><code class="lang-plaintext">resource "aws_subnet" "public" {
  count = 3

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name  = "public-subnet-${count.index + 1}"
    Index = count.index
  }
}

# Creates:
# - public[0]: 10.0.1.0/24 in us-east-1a
# - public[1]: 10.0.2.0/24 in us-east-1b
# - public[2]: 10.0.3.0/24 in us-east-1c
</code></pre>
<h3 id="heading-dynamic-count-with-variables"><strong>Dynamic Count with Variables</strong></h3>
<pre><code class="lang-plaintext">variable "instance_count" {
  type    = number
  default = 3
}

resource "aws_instance" "app" {
  count = var.instance_count

  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
}
</code></pre>
<h3 id="heading-conditional-count-create-or-not"><strong>Conditional Count (Create or Not)</strong></h3>
<pre><code class="lang-plaintext">variable "create_instance" {
  type    = bool
  default = true
}

resource "aws_instance" "optional" {
  count = var.create_instance ? 1 : 0

  ami           = "ami-12345"
  instance_type = "t2.micro"
}

# If true: creates 1 instance
# If false: creates 0 instances (none)
</code></pre>
<h3 id="heading-count-with-length"><strong>Count with length()</strong></h3>
<pre><code class="lang-plaintext">variable "availability_zones" {
  type = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

resource "aws_subnet" "public" {
  count = length(var.availability_zones)

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index}.0/24"
  availability_zone = var.availability_zones[count.index]
}
</code></pre>
<h3 id="heading-referencing-count-resources"><strong>Referencing Count Resources</strong></h3>
<pre><code class="lang-plaintext"># Reference all instances
output "all_instance_ids" {
  value = aws_instance.web[*].id
}

# Reference specific instance
output "first_instance_id" {
  value = aws_instance.web[0].id
}

# Use in another resource
resource "aws_eip" "web" {
  count    = length(aws_instance.web)
  instance = aws_instance.web[count.index].id
}
</code></pre>
<h2 id="heading-the-foreach-meta-argument">🔁 The For_Each Meta-Argument</h2>
<p><strong>For_each</strong> creates multiple resources based on a map or set.</p>
<h3 id="heading-basic-foreach-syntax"><strong>Basic For_Each Syntax</strong></h3>
<pre><code class="lang-plaintext">resource "aws_instance" "servers" {
  for_each = toset(["web", "api", "worker"])

  ami           = "ami-12345"
  instance_type = "t2.micro"

  tags = {
    Name = "server-${each.key}"
  }
}

# Creates: servers["web"], servers["api"], servers["worker"]
</code></pre>
<h3 id="heading-eachkey-and-eachvalue"><strong>each.key and each.value</strong></h3>
<pre><code class="lang-plaintext"># With set: each.key == each.value
for_each = toset(["web", "api"])
# each.key = "web", each.value = "web"

# With map: each.key = key, each.value = value
for_each = {
  web = "t2.micro"
  api = "t2.small"
}
# each.key = "web", each.value = "t2.micro"
</code></pre>
<h3 id="heading-foreach-with-maps"><strong>For_Each with Maps</strong></h3>
<pre><code class="lang-plaintext">variable "instances" {
  type = map(object({
    instance_type = string
    ami           = string
  }))

  default = {
    web = {
      instance_type = "t2.micro"
      ami           = "ami-12345"
    }
    api = {
      instance_type = "t2.small"
      ami           = "ami-67890"
    }
    worker = {
      instance_type = "t2.micro"
      ami           = "ami-12345"
    }
  }
}

resource "aws_instance" "servers" {
  for_each = var.instances

  ami           = each.value.ami
  instance_type = each.value.instance_type

  tags = {
    Name = each.key
    Type = each.value.instance_type
  }
}
</code></pre>
<h3 id="heading-foreach-with-sets"><strong>For_Each with Sets</strong></h3>
<pre><code class="lang-plaintext">variable "subnet_names" {
  type    = set(string)
  default = ["public-1", "public-2", "private-1"]
}

resource "aws_subnet" "subnets" {
  for_each = var.subnet_names

  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.${index(var.subnet_names, each.key)}.0/24"

  tags = {
    Name = each.key
  }
}
</code></pre>
<h3 id="heading-referencing-foreach-resources"><strong>Referencing For_Each Resources</strong></h3>
<pre><code class="lang-plaintext"># Reference all instances
output "all_instance_ids" {
  value = values(aws_instance.servers)[*].id
}

# Reference specific instance
output "web_instance_id" {
  value = aws_instance.servers["web"].id
}

# Create map of IDs
output "instance_id_map" {
  value = {
    for key, instance in aws_instance.servers :
    key =&gt; instance.id
  }
}

# Use in another resource
resource "aws_eip" "server_ips" {
  for_each = aws_instance.servers

  instance = each.value.id

  tags = {
    Name = "eip-${each.key}"
  }
}
</code></pre>
<h2 id="heading-count-vs-foreach-when-to-use-which">⚖️ Count vs For_Each: When to Use Which</h2>
<h3 id="heading-use-count-when"><strong>Use Count When:</strong></h3>
<p>✅ Creating a specific number of identical resources</p>
<pre><code class="lang-plaintext">resource "aws_instance" "web" {
  count = 5  # Create exactly 5 instances
}
</code></pre>
<p>✅ Conditional resource creation (0 or 1)</p>
<pre><code class="lang-plaintext">count = var.create_bastion ? 1 : 0
</code></pre>
<p>✅ Resources are truly identical and order doesn’t matter</p>
<pre><code class="lang-plaintext">resource "aws_subnet" "public" {
  count = 3
}
</code></pre>
<h3 id="heading-use-foreach-when"><strong>Use For_Each When:</strong></h3>
<p>✅ Each resource has unique configuration</p>
<pre><code class="lang-plaintext">for_each = {
  web = { type = "t2.micro", ami = "ami-123" }
  api = { type = "t2.small", ami = "ami-456" }
}
</code></pre>
<p>✅ Resources are identified by name/key</p>
<pre><code class="lang-plaintext">for_each = toset(["production", "staging", "development"])
</code></pre>
<p>✅ You need to add/remove specific items</p>
<pre><code class="lang-plaintext"># Removing "staging" won't affect "production"
for_each = toset(["production", "development"])
</code></pre>
<h3 id="heading-the-key-difference"><strong>The Key Difference</strong></h3>
<pre><code class="lang-plaintext"># COUNT: Resources identified by index
aws_instance.web[0]
aws_instance.web[1]
aws_instance.web[2]

# If you remove the middle item, indices shift!
# web[1] becomes what was web[2]

# FOR_EACH: Resources identified by key
aws_instance.servers["web"]
aws_instance.servers["api"]
aws_instance.servers["worker"]

# Removing "api" doesn't affect "web" or "worker"
</code></pre>
<h2 id="heading-1-using-count">⚙️ 1️⃣ Using <code>count</code></h2>
<h3 id="heading-example">Example:</h3>
<pre><code class="lang-plaintext">resource "aws_instance" "web" {
  count = 3

  ami           = "ami-123456"
  instance_type = "t2.micro"
}
</code></pre>
<h3 id="heading-what-terraform-does">✅ What Terraform does:</h3>
<ul>
<li><p>Creates <strong>3 resources</strong>, indexed by <strong>number</strong> (starting from 0):</p>
<pre><code class="lang-plaintext">  aws_instance.web[0]
  aws_instance.web[1]
  aws_instance.web[2]
</code></pre>
</li>
</ul>
<h3 id="heading-access-example">🧩 Access Example:</h3>
<p>You can reference a specific one using its index:</p>
<pre><code class="lang-plaintext">aws_instance.web[1].id
</code></pre>
<h3 id="heading-the-problem">⚠️ The Problem:</h3>
<p>If you remove or reorder items (for example, if you reduce count to 2 or skip the middle one),<br />Terraform <strong>reindexes</strong> the remaining ones.</p>
<p>So if you had:</p>
<pre><code class="lang-plaintext">aws_instance.web[0]  -&gt; stays
aws_instance.web[1]  -&gt; deleted
aws_instance.web[2]  -&gt; becomes web[1]
</code></pre>
<p>Result: Terraform may <strong>destroy and recreate</strong> resources unexpectedly because the <strong>index numbers shift</strong>.</p>
<h3 id="heading-best-for">💡 Best for:</h3>
<p>Simple, fixed-size lists—e.g., creating N identical servers.</p>
<h2 id="heading-2-using-foreach">⚙️ 2️⃣ Using <code>for_each</code></h2>
<h3 id="heading-example-1">Example:</h3>
<pre><code class="lang-plaintext">resource "aws_instance" "servers" {
  for_each = {
    web    = "t2.micro"
    api    = "t2.small"
    worker = "t3.micro"
  }

  ami           = "ami-123456"
  instance_type = each.value
  tags = {
    Name = each.key
  }
}
</code></pre>
<h3 id="heading-what-terraform-does-1">✅ What Terraform does:</h3>
<p>Creates resources identified by <strong>key names</strong>, not numbers:</p>
<pre><code class="lang-plaintext">aws_instance.servers["web"]
aws_instance.servers["api"]
aws_instance.servers["worker"]
</code></pre>
<p>Each one is <strong>uniquely named and stable</strong> — based on its key.</p>
<hr />
<h3 id="heading-access-example-1">🧩 Access Example:</h3>
<p>You can reference a specific one by its key:</p>
<pre><code class="lang-plaintext">aws_instance.servers["web"].id
</code></pre>
<h3 id="heading-the-advantage">💪 The Advantage:</h3>
<p>If you remove <code>"api"</code> from the map, Terraform <strong>only deletes that one</strong>:</p>
<pre><code class="lang-plaintext">aws_instance.servers["api"]  -&gt; destroyed
</code></pre>
<p>✅ <code>"web"</code> and <code>"worker"</code> stay exactly the same — <strong>no index shifting</strong>, no recreation.</p>
<hr />
<h2 id="heading-the-key-difference-summary">🧠 The Key Difference (Summary)</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td><code>count</code></td><td><code>for_each</code></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Identification</strong></td><td>By <strong>index number</strong> (<code>[0]</code>, <code>[1]</code>, <code>[2]</code>)</td><td>By <strong>key name</strong> (<code>["web"]</code>, <code>["api"]</code>)</td></tr>
<tr>
<td><strong>Input type</strong></td><td>List or number</td><td>Map or set</td></tr>
<tr>
<td><strong>Reference syntax</strong></td><td><code>aws_instance.web[0]</code></td><td><code>aws_instance.web["web"]</code></td></tr>
<tr>
<td><strong>When to use</strong></td><td>Fixed-size or ordered lists</td><td>Named, key-based, or dynamic collections</td></tr>
<tr>
<td><strong>If item removed</strong></td><td>Indices shift → may recreate wrong instances</td><td>Only that key’s resource is destroyed</td></tr>
<tr>
<td><strong>Stability</strong></td><td>Fragile (depends on order)</td><td>Stable (depends on key)</td></tr>
</tbody>
</table>
</div><h2 id="heading-common-pitfalls-and-solutions">⚠️ Common Pitfalls and Solutions</h2>
<h3 id="heading-pitfall-1-count-index-shift"><strong>Pitfall 1: Count Index Shift</strong></h3>
<pre><code class="lang-plaintext"># ❌ BAD: Using count with list
variable "servers" {
  default = ["web", "api", "db"]
}

resource "aws_instance" "bad" {
  count = length(var.servers)
  # If you remove "api", "db" shifts from index 2 to 1
  # Terraform will destroy and recreate it!
}

# ✅ GOOD: Use for_each
resource "aws_instance" "good" {
  for_each = toset(var.servers)
  # Removing "api" doesn't affect "web" or "db"
}
</code></pre>
<h3 id="heading-pitfall-2-cant-use-both"><strong>Pitfall 2: Can’t Use Both</strong></h3>
<pre><code class="lang-plaintext"># ❌ ERROR: Cannot use both
resource "aws_instance" "invalid" {
  count    = 3
  for_each = toset(["a", "b"])  # ERROR!
}
</code></pre>
<h3 id="heading-pitfall-3-foreach-requires-map-or-set"><strong>Pitfall 3: For_Each Requires Map or Set</strong></h3>
<pre><code class="lang-plaintext"># ❌ ERROR: for_each needs map or set, not list
variable "azs" {
  default = ["us-east-1a", "us-east-1b"]
}

resource "aws_subnet" "bad" {
  for_each = var.azs  # ERROR! List not supported
}

# ✅ GOOD: Convert to set
resource "aws_subnet" "good" {
  for_each = toset(var.azs)
}
</code></pre>
<h2 id="heading-hands-on-lab-count-vs-foreach">🧪 Hands-On Lab: Count vs For_Each</h2>
<p>Let’s build infrastructure demonstrating both approaches!</p>
<h3 id="heading-step-1-create-project"><strong>Step 1: Create Project</strong></h3>
<pre><code class="lang-bash">mkdir terraform-count-foreach-lab
<span class="hljs-built_in">cd</span> terraform-count-foreach-lab
</code></pre>
<h3 id="heading-step-2-create-variablestfhttpvariablestf"><strong>Step 2: Create</strong> <a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a></h3>
<pre><code class="lang-plaintext"># variables.tf

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "environment" {
  type    = string
  default = "dev"
}

# For count example
variable "public_subnet_count" {
  type    = number
  default = 3
}

variable "availability_zones" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

# For for_each example
variable "applications" {
  type = map(object({
    instance_type = string
    desired_count = number
    port          = number
  }))

  default = {
    web = {
      instance_type = "t2.micro"
      desired_count = 2
      port          = 80
    }
    api = {
      instance_type = "t2.small"
      desired_count = 2
      port          = 8080
    }
    worker = {
      instance_type = "t2.micro"
      desired_count = 3
      port          = 0
    }
  }
}

variable "create_bastion" {
  type    = bool
  default = true
}
</code></pre>
<h3 id="heading-step-3-create-maintfhttpmaintf"><strong>Step 3: Create</strong> <a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-plaintext"># main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "count-foreach-demo-vpc"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "main-igw"
  }
}

# ============================================
# COUNT EXAMPLES
# ============================================

# Public Subnets using COUNT
resource "aws_subnet" "public" {
  count = var.public_subnet_count

  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.${count.index + 1}.0/24"
  availability_zone       = var.availability_zones[count.index % length(var.availability_zones)]
  map_public_ip_on_launch = true

  tags = {
    Name  = "public-subnet-${count.index + 1}"
    Index = count.index
  }
}

# Conditional Bastion using COUNT
resource "aws_instance" "bastion" {
  count = var.create_bastion ? 1 : 0

  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "bastion-host"
  }
}

# ============================================
# FOR_EACH EXAMPLES
# ============================================

# Application-specific Subnets using FOR_EACH
locals {
  app_subnets = {
    for app_name in keys(var.applications) :
    app_name =&gt; {
      cidr_block = "10.0.${10 + index(keys(var.applications), app_name)}.0/24"
      az         = var.availability_zones[0]
    }
  }
}

resource "aws_subnet" "app_subnets" {
  for_each = local.app_subnets

  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value.cidr_block
  availability_zone = each.value.az

  tags = {
    Name        = "${each.key}-subnet"
    Application = each.key
  }
}

# Security Groups using FOR_EACH
resource "aws_security_group" "app_sgs" {
  for_each = var.applications

  name        = "${each.key}-sg"
  description = "Security group for ${each.key}"
  vpc_id      = aws_vpc.main.id

  dynamic "ingress" {
    for_each = each.value.port &gt; 0 ? [1] : []
    content {
      from_port   = each.value.port
      to_port     = each.value.port
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name        = "${each.key}-sg"
    Application = each.key
  }
}

# AMI Data Source
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# Application Instances using nested COUNT and FOR_EACH
locals {
  # Flatten applications into individual instances
  app_instances = flatten([
    for app_name, app_config in var.applications : [
      for i in range(app_config.desired_count) : {
        key           = "${app_name}-${i}"
        app_name      = app_name
        instance_type = app_config.instance_type
        index         = i
      }
    ]
  ])

  # Convert to map for for_each
  app_instances_map = {
    for inst in local.app_instances :
    inst.key =&gt; inst
  }
}

resource "aws_instance" "app_instances" {
  for_each = local.app_instances_map

  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = each.value.instance_type
  subnet_id              = aws_subnet.app_subnets[each.value.app_name].id
  vpc_security_group_ids = [aws_security_group.app_sgs[each.value.app_name].id]

  user_data = &lt;&lt;-EOF
              #!/bin/bash
              echo "${each.value.app_name} instance ${each.value.index}" &gt; /tmp/info.txt
              EOF

  tags = {
    Name        = each.key
    Application = each.value.app_name
    Index       = each.value.index
  }
}
</code></pre>
<h3 id="heading-step-4-create-outputstfhttpoutputstf"><strong>Step 4: Create</strong> <a target="_blank" href="http://outputs.tf"><strong>outputs.tf</strong></a></h3>
<pre><code class="lang-plaintext"># outputs.tf

# COUNT outputs
output "public_subnet_ids_count" {
  description = "Public subnet IDs (created with count)"
  value       = aws_subnet.public[*].id
}

output "bastion_instance_id" {
  description = "Bastion instance ID (conditional with count)"
  value       = length(aws_instance.bastion) &gt; 0 ? aws_instance.bastion[0].id : "Not created"
}

# FOR_EACH outputs
output "app_subnet_ids_foreach" {
  description = "App subnet IDs (created with for_each)"
  value = {
    for key, subnet in aws_subnet.app_subnets :
    key =&gt; subnet.id
  }
}

output "security_group_ids_foreach" {
  description = "Security group IDs (created with for_each)"
  value = {
    for key, sg in aws_security_group.app_sgs :
    key =&gt; sg.id
  }
}

output "app_instances_by_type" {
  description = "Instances grouped by application"
  value = {
    for app_name in keys(var.applications) :
    app_name =&gt; [
      for key, instance in aws_instance.app_instances :
      {
        id         = instance.id
        private_ip = instance.private_ip
      }
      if local.app_instances_map[key].app_name == app_name
    ]
  }
}

output "total_instance_count" {
  description = "Total number of instances"
  value = {
    bastion = var.create_bastion ? 1 : 0
    apps    = sum([for app in var.applications : app.desired_count])
    total   = (var.create_bastion ? 1 : 0) + sum([for app in var.applications : app.desired_count])
  }
}
</code></pre>
<h3 id="heading-step-5-test-count-behavior"><strong>Step 5: Test Count Behavior</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init
<span class="hljs-comment"># Applyt</span>
erraform apply -auto-approve
<span class="hljs-comment"># View outputs</span>
terraform output
<span class="hljs-comment"># Now test count index shift issue</span>
<span class="hljs-comment"># Modify variables.tf: change public_subnet_count from 3 to 2</span>
<span class="hljs-comment"># Plan again</span>
terraform plan
<span class="hljs-comment"># Notice: Terraform wants to destroy public[2]</span>
<span class="hljs-comment"># If resources depend on indices, this can be problematic!</span>
</code></pre>
<h3 id="heading-step-6-test-foreach-behavior"><strong>Step 6: Test For_Each Behavior</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Remove one application from variables.tf</span>
<span class="hljs-comment"># Change applications map to remove "api"</span>
terraform plan
<span class="hljs-comment"># Notice: Only "api" resources are destroyed</span>
<span class="hljs-comment"># "web" and "worker" are unchanged!</span>
</code></pre>
<h3 id="heading-step-7-clean-up"><strong>Step 7: Clean Up</strong></h3>
<pre><code class="lang-bash">terraform destroy -auto-approve
</code></pre>
<h2 id="heading-best-practices">📝 Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Prefer for_each for named resources</strong></p>
<pre><code class="lang-plaintext"> for_each = toset(["web", "api", "worker"])
</code></pre>
</li>
<li><p><strong>Use count for conditional creation</strong></p>
<pre><code class="lang-plaintext"> count = var.enabled ? 1 : 0
</code></pre>
</li>
<li><p><strong>Use count for simple multiples</strong></p>
<pre><code class="lang-plaintext"> count = 5  # When you just need 5 identical things
</code></pre>
</li>
<li><p><strong>Convert lists to sets for for_each</strong></p>
<pre><code class="lang-plaintext"> for_each = toset(var.list)
</code></pre>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p><strong>Don’t use count with lists that might change</strong></p>
</li>
<li><p><strong>Don’t use both count and for_each together</strong></p>
</li>
<li><p><strong>Don’t make count/for_each depend on resource attributes</strong></p>
</li>
</ol>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Count meta-argument and count.index</p>
</li>
<li><p>✅ For_each meta-argument with maps and sets</p>
</li>
<li><p>✅ each.key and each.value</p>
</li>
<li><p>✅ When to use count vs for_each</p>
</li>
<li><p>✅ Common pitfalls and solutions</p>
</li>
<li><p>✅ Resource referencing patterns</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 13: Conditional Expressions &amp; Logic</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Master conditional expressions</p>
</li>
<li><p>Learn ternary operators</p>
</li>
<li><p>Use logical operators</p>
</li>
<li><p>Implement complex conditions</p>
</li>
<li><p>Build conditional infrastructure</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-11-working-with-lists-maps-and-sets">← Day 11: Lists, Maps &amp; Sets</a> | <a target="_blank" href="day13.md">Day 13: Conditionals &amp; Logic →</a></p>
<hr />
<p><em>Remember: Choose for_each for flexibility, count for simplicity!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 11: Working with Lists, Maps, and Sets]]></title><description><![CDATA[Welcome to Day 11! Today we’ll master Terraform’s collection types and manipulation techniques. You’ll learn powerful expressions that make your infrastructure configurations dynamic and flexible.
🎯 Today’s Goals

Master collection manipulation (lis...]]></description><link>https://stackopsdiary.site/day-11-working-with-lists-maps-and-sets</link><guid isPermaLink="true">https://stackopsdiary.site/day-11-working-with-lists-maps-and-sets</guid><category><![CDATA[Terraform]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Fri, 17 Oct 2025 12:30:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760697412526/2450befe-78a1-49a7-8e3c-cbaff3ad8942.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 11! Today we’ll master Terraform’s collection types and manipulation techniques. You’ll learn powerful expressions that make your infrastructure configurations dynamic and flexible.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Master collection manipulation (lists, maps, sets)</p>
</li>
<li><p>Learn for expressions and splat expressions</p>
</li>
<li><p>Use collection functions effectively</p>
</li>
<li><p>Transform and filter data</p>
</li>
<li><p>Build dynamic, data-driven configurations</p>
</li>
</ul>
<h2 id="heading-collection-types-review">📋 Collection Types Review</h2>
<pre><code class="lang-plaintext"># List - ordered, indexed
variable "azs" {
  type = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

# Map - key-value pairs
variable "instance_types" {
  type = map(string)
  default = {
    dev  = "t2.micro"
    prod = "t3.large"
  }
}

# Set - unique, unordered
variable "security_groups" {
  type = set(string)
  default = ["sg-123", "sg-456"]
}
</code></pre>
<h2 id="heading-for-expressions">🔄 For Expressions</h2>
<p><strong>For expressions</strong> transform and filter collections.</p>
<h3 id="heading-syntax"><strong>Syntax</strong></h3>
<pre><code class="lang-plaintext"># List comprehension
[for item in list : transformation]

# Map comprehension
{for key, value in map : new_key =&gt; new_value}

# Conditional filtering
[for item in list : item if condition]
</code></pre>
<h3 id="heading-list-to-list"><strong>List to List</strong></h3>
<pre><code class="lang-plaintext">variable "names" {
  default = ["alice", "bob", "charlie"]
}

locals {
  # Convert to uppercase
  upper_names = [for name in var.names : upper(name)]
  # Result: ["ALICE", "BOB", "CHARLIE"]

  # Add prefix
  prefixed_names = [for name in var.names : "user-${name}"]
  # Result: ["user-alice", "user-bob", "user-charlie"]

  # Filter and transform
  long_names = [for name in var.names : upper(name) if length(name) &gt; 3]
  # Result: ["ALICE", "CHARLIE"]
}
</code></pre>
<h3 id="heading-list-to-map"><strong>List to Map</strong></h3>
<pre><code class="lang-plaintext">variable "users" {
  default = ["alice", "bob", "charlie"]
}

locals {
  # Create map from list
  user_map = {
    for user in var.users :
    user =&gt; {
      email = "${user}@example.com"
      role  = "developer"
    }
  }
  # Result: {
  #   alice = { email = "alice@example.com", role = "developer" }
  #   bob = { email = "bob@example.com", role = "developer" }
  #   ...
  # }
}
</code></pre>
<h3 id="heading-map-to-list"><strong>Map to List</strong></h3>
<pre><code class="lang-plaintext">variable "servers" {
  default = {
    web = { ip = "10.0.1.10", port = 80 }
    api = { ip = "10.0.2.10", port = 8080 }
  }
}

locals {
  # Extract IPs
  server_ips = [for name, config in var.servers : config.ip]
  # Result: ["10.0.1.10", "10.0.2.10"]

  # Create connection strings
  connections = [
    for name, config in var.servers :
    "${config.ip}:${config.port}"
  ]
  # Result: ["10.0.1.10:80", "10.0.2.10:8080"]
}
</code></pre>
<h4 id="heading-this-part">This part:</h4>
<ul>
<li><p>Uses a <strong>for-expression</strong> to loop through <code>var.servers</code>.</p>
</li>
<li><p>For each server:</p>
<ul>
<li><p><code>name</code> is the key (<code>web</code>, <code>api</code>).</p>
</li>
<li><p><code>config</code> is the value (<code>{ ip = "10.0.1.10", port = 80 }</code>).</p>
</li>
</ul>
</li>
<li><p>It extracts only the <code>ip</code> field from each server config.</p>
</li>
</ul>
<h3 id="heading-complex-transformations"><strong>Complex Transformations</strong></h3>
<h3 id="heading-step-1-define-the-variable">🧱 Step 1: Define the variable</h3>
<pre><code class="lang-plaintext">variable "subnets" {
  default = [
    { cidr = "10.0.1.0/24", az = "us-east-1a", type = "public" },
    { cidr = "10.0.2.0/24", az = "us-east-1b", type = "public" },
    { cidr = "10.0.10.0/24", az = "us-east-1a", type = "private" }
  ]
}
</code></pre>
<h3 id="heading-what-this-means">🧩 What this means:</h3>
<ul>
<li><p><code>subnets</code> is a <strong>list of maps</strong> (a list of objects).</p>
</li>
<li><p>Each subnet has three attributes:</p>
<ul>
<li><p><code>cidr</code> → CIDR block of the subnet.</p>
</li>
<li><p><code>az</code> → Availability Zone.</p>
</li>
<li><p><code>type</code> → Whether it’s public or private.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-visual-structure">📊 Visual structure:</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Index</td><td>CIDR</td><td>AZ</td><td>Type</td></tr>
</thead>
<tbody>
<tr>
<td>0</td><td>10.0.1.0/24</td><td>us-east-1a</td><td>public</td></tr>
<tr>
<td>1</td><td>10.0.2.0/24</td><td>us-east-1b</td><td>public</td></tr>
<tr>
<td>2</td><td>10.0.10.0/24</td><td>us-east-1a</td><td>private</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-step-2-locals-computed-values">🧮 Step 2: Locals (computed values)</h2>
<p>Now you define <strong>local variables</strong> to transform or filter that data.</p>
<hr />
<h3 id="heading-1-filter-public-subnets">🟩 (1) Filter public subnets</h3>
<pre><code class="lang-plaintext">public_subnets = [
  for subnet in var.subnets :
  subnet if subnet.type == "public"
]
</code></pre>
<h3 id="heading-explanation">🔍 Explanation:</h3>
<ul>
<li><p>Terraform loops through every element in <code>var.subnets</code>.</p>
</li>
<li><p>Each element is temporarily named <code>subnet</code>.</p>
</li>
<li><p>The <code>if</code> condition filters only subnets where <code>subnet.type == "public"</code>.</p>
</li>
</ul>
<h3 id="heading-result">✅ Result:</h3>
<pre><code class="lang-plaintext">[
  { cidr = "10.0.1.0/24", az = "us-east-1a", type = "public" },
  { cidr = "10.0.2.0/24", az = "us-east-1b", type = "public" }
]
</code></pre>
<p>So you now have a list that <strong>only contains the public subnets</strong>.</p>
<hr />
<h3 id="heading-2-transform-to-a-specific-structure">🟦 (2) Transform to a specific structure</h3>
<pre><code class="lang-plaintext">subnet_configs = [
  for idx, subnet in var.subnets : {
    name = "subnet-${idx}"
    cidr = subnet.cidr
    az   = subnet.az
    tags = {
      Type  = subnet.type
      Index = idx
    }
  }
]
</code></pre>
<h3 id="heading-explanation-1">🔍 Explanation:</h3>
<p>This is another <strong>for-expression</strong>, but this time:</p>
<ul>
<li><p>It uses both the <strong>index (</strong><code>idx</code>) and the <strong>item (</strong><code>subnet</code>).</p>
</li>
<li><p>For every subnet, Terraform builds a <strong>new object</strong> with a custom structure.</p>
</li>
</ul>
<p>Let’s break it down:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Field</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>name</code></td><td>A dynamic name based on index → <code>"subnet-0"</code>, <code>"subnet-1"</code>, etc.</td></tr>
<tr>
<td><code>cidr</code></td><td>Directly taken from the original subnet.</td></tr>
<tr>
<td><code>az</code></td><td>Directly taken from the original subnet.</td></tr>
<tr>
<td><code>tags</code></td><td>A nested map that includes:</td></tr>
<tr>
<td>• The <code>Type</code> (<code>public</code>/<code>private</code>)</td><td></td></tr>
<tr>
<td>• The index value</td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-resulting-localsubnetconfigs">✅ Resulting <code>local.subnet_configs</code>:</h3>
<pre><code class="lang-plaintext">[
  {
    name = "subnet-0"
    cidr = "10.0.1.0/24"
    az   = "us-east-1a"
    tags = {
      Type  = "public"
      Index = 0
    }
  },
  {
    name = "subnet-1"
    cidr = "10.0.2.0/24"
    az   = "us-east-1b"
    tags = {
      Type  = "public"
      Index = 1
    }
  },
  {
    name = "subnet-2"
    cidr = "10.0.10.0/24"
    az   = "us-east-1a"
    tags = {
      Type  = "private"
      Index = 2
    }
  }
]
</code></pre>
<h2 id="heading-essential-collection-functions">🛠️ Essential Collection Functions</h2>
<h3 id="heading-length-and-size"><strong>Length and Size</strong></h3>
<pre><code class="lang-plaintext">length(list)      # Number of elements
length(map)       # Number of keys
length(string)    # Number of characters

#Example
locals {
  az_count      = length(var.availability_zones)  # 3
  subnet_count  = length(var.subnets)             # 5
  name_length   = length("terraform")             # 9
}
</code></pre>
<h3 id="heading-element-and-index"><strong>Element and Index</strong></h3>
<pre><code class="lang-plaintext">element(list, index)  # Get element (wraps around)
index(list, value)    # Find index of value

locals {
  first_az  = element(var.availability_zones, 0)
  # Safe access - wraps around
  safe_az   = element(var.availability_zones, 10)  
  # Wraps to index 1 if list has 3 items
  # Find position
  az_index  = index(var.availability_zones, "us-east-1b")  # 1
}
</code></pre>
<p>Let’s go line by line.</p>
<hr />
<h3 id="heading-1-firstaz-elementvaravailabilityzones-0">1️⃣ <code>first_az = element(var.availability_zones, 0)</code></h3>
<ul>
<li><p>Picks the <strong>first element</strong> (index <code>0</code>).</p>
</li>
<li><p>Result:</p>
<pre><code class="lang-plaintext">  "us-east-1a"
</code></pre>
</li>
</ul>
<hr />
<h3 id="heading-2-safeaz-elementvaravailabilityzones-10">2️⃣ <code>safe_az = element(var.availability_zones, 10)</code></h3>
<ul>
<li><p>The list has <strong>3 items</strong>, but index <code>10</code> is out of range.</p>
</li>
<li><p>Terraform <strong>wraps around</strong> using modulo math:</p>
<pre><code class="lang-plaintext">  10 % 3 = 1
  # Result 
  "us-east-1b"
</code></pre>
</li>
<li><p>So it returns the element at index <code>1</code>.</p>
</li>
<li><p>This is why it’s called “safe access” — it won’t crash even if you go out of range.</p>
</li>
</ul>
<hr />
<h3 id="heading-3-azindex-indexvaravailabilityzones-us-east-1b">3️⃣ <code>az_index = index(var.availability_zones, "us-east-1b")</code></h3>
<ul>
<li><p>Finds the <strong>position</strong> of <code>"us-east-1b"</code> in the list.</p>
</li>
<li><p><code>"us-east-1b"</code> is at position 1 (0-based).</p>
</li>
</ul>
<p>✅ <strong>Result:</strong></p>
<pre><code class="lang-plaintext">1
</code></pre>
<hr />
<h2 id="heading-summary-table">🧠 Summary Table</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Function</td><td>Purpose</td><td>Example</td><td>Result</td></tr>
</thead>
<tbody>
<tr>
<td><code>element(list, index)</code></td><td>Get element (wraps around if out of range)</td><td><code>element(["a","b","c"], 4)</code></td><td><code>"b"</code></td></tr>
<tr>
<td><code>index(list, value)</code></td><td>Find index (position) of value</td><td><code>index(["a","b","c"], "b")</code></td><td><code>1</code></td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-contains-and-keysvalues"><strong>Contains and Keys/Values</strong></h3>
<pre><code class="lang-plaintext">contains(list, value)  # Check if list contains value
keys(map)              # Get all keys
values(map)            # Get all values

locals {
  has_prod = contains(["dev", "staging", "prod"], var.environment)

  # Map operations
  env_names = keys(var.instance_types)    # ["dev", "prod"]
  types     = values(var.instance_types)  # ["t2.micro", "t3.large"]
}
</code></pre>
<h3 id="heading-distinct-and-sort"><strong>Distinct and Sort</strong></h3>
<pre><code class="lang-plaintext">distinct(list)  # Remove duplicates
sort(list)      # Sort alphabetically

locals {
  # Remove duplicates
  unique_azs = distinct(["us-east-1a", "us-east-1b", "us-east-1a"])
  # Result: ["us-east-1a", "us-east-1b"]

  # Sort
  sorted_names = sort(["charlie", "alice", "bob"])
  # Result: ["alice", "bob", "charlie"]

  # Reverse sort
  reverse_sorted = reverse(sort(var.names))
}
</code></pre>
<h3 id="heading-lookup"><strong>Lookup</strong></h3>
<p>Safely get a value from a map — <strong>with a default fallback</strong> if the key doesn’t exist.<br />Unlike direct access (<a target="_blank" href="http://var.map"><code>var.map</code></a><code>["key"]</code>), <code>lookup()</code> won’t fail when the key is missing.</p>
<h3 id="heading-syntax-1">🔹 Syntax:</h3>
<pre><code class="lang-plaintext">lookup(map, key, default)
</code></pre>
<hr />
<h3 id="heading-example-1-basic-lookup">💡 Example 1 — Basic lookup</h3>
<pre><code class="lang-plaintext">variable "instance_types" {
  default = {
    dev  = "t2.micro"
    prod = "t3.large"
  }
}

variable "environment" {
  default = "staging"
}

locals {
  instance_type = lookup(
    var.instance_types,
    var.environment,
    "t2.micro"  # default
  )
}
</code></pre>
<p>✅ Since <code>"staging"</code> isn’t a key in <code>var.instance_types</code>,<br /><code>local.instance_type = "t2.micro"</code></p>
<hr />
<h3 id="heading-example-2-safe-nested-lookup">💡 Example 2 — Safe nested lookup</h3>
<pre><code class="lang-plaintext">variable "database_config" {
  default = {
    dev = { port = 3306 }
    prod = { port = 5432 }
  }
}

variable "environment" {
  default = "qa"
}

locals {
  db_port = lookup(
    lookup(var.database_config, var.environment, {}),
    "port",
    5432
  )
}
</code></pre>
<p>Explanation:</p>
<ul>
<li><p>The first <code>lookup()</code> tries to get the <code>qa</code> configuration from <code>var.database_config</code>.<br />  If not found, returns <code>{}</code> (an empty map).</p>
</li>
<li><p>The second <code>lookup()</code> then tries to get <code>"port"</code> from that map.<br />  If it’s missing, returns <code>5432</code> as the default.</p>
</li>
</ul>
<p>✅ <strong>Result:</strong><br /><code>db_port = 5432</code></p>
<h2 id="heading-hands-on-lab-collection-mastery">🧪 Hands-On Lab: Collection Mastery</h2>
<p>Let’s build a complex infrastructure using advanced collection techniques!</p>
<h3 id="heading-step-1-create-project"><strong>Step 1: Create Project</strong></h3>
<pre><code class="lang-bash">mkdir terraform-collections-lab
<span class="hljs-built_in">cd</span> terraform-collections-lab
</code></pre>
<h3 id="heading-step-2-create-variablestfhttpvariablestf"><strong>Step 2: Create</strong> <a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a></h3>
<pre><code class="lang-plaintext"># variables.tf

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "project_name" {
  type    = string
  default = "collections-demo"
}

variable "availability_zones" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

variable "subnet_configurations" {
  description = "List of subnet configurations"
  type = list(object({
    name = string
    cidr = string
    type = string
    tier = string
  }))

  default = [
    { name = "web-1", cidr = "10.0.1.0/24", type = "public", tier = "web" },
    { name = "web-2", cidr = "10.0.2.0/24", type = "public", tier = "web" },
    { name = "app-1", cidr = "10.0.11.0/24", type = "private", tier = "app" },
    { name = "app-2", cidr = "10.0.12.0/24", type = "private", tier = "app" },
    { name = "db-1", cidr = "10.0.21.0/24", type = "private", tier = "db" },
    { name = "db-2", cidr = "10.0.22.0/24", type = "private", tier = "db" }
  ]
}

variable "instance_configs" {
  description = "Instance configurations per tier"
  type = map(object({
    instance_type = string
    count         = number
    user_data     = string
  }))

  default = {
    web = {
      instance_type = "t2.micro"
      count         = 2
      user_data     = "#!/bin/bash\\necho 'Web Server' &gt; /var/www/html/index.html"
    }
    app = {
      instance_type = "t2.small"
      count         = 2
      user_data     = "#!/bin/bash\\necho 'App Server'"
    }
  }
}

variable "common_tags" {
  type = map(string)
  default = {
    Project   = "Collections Demo"
    ManagedBy = "Terraform"
  }
}
</code></pre>
<h3 id="heading-step-3-create-localstfhttplocalstf"><strong>Step 3: Create</strong> <a target="_blank" href="http://locals.tf"><strong>locals.tf</strong></a></h3>
<pre><code class="lang-plaintext"># locals.tf

locals {
  # Filter subnets by type
  public_subnets = [
    for subnet in var.subnet_configurations :
    subnet if subnet.type == "public"
  ]

  private_subnets = [
    for subnet in var.subnet_configurations :
    subnet if subnet.type == "private"
  ]

  # Group subnets by tier
  subnets_by_tier = {
    for tier in distinct([for s in var.subnet_configurations : s.tier]) :
    tier =&gt; [
      for subnet in var.subnet_configurations :
      subnet if subnet.tier == tier
    ]
  }

  # Create subnet map for easy lookup
  subnet_map = {
    for subnet in var.subnet_configurations :
    subnet.name =&gt; subnet
  }

  # Get all tiers
  all_tiers = distinct([for s in var.subnet_configurations : s.tier])

  # Get all CIDR blocks
  all_cidrs = [for s in var.subnet_configurations : s.cidr]

  # Distribute subnets across AZs
  subnet_az_mapping = {
    for idx, subnet in var.subnet_configurations :
    subnet.name =&gt; var.availability_zones[idx % length(var.availability_zones)]
  }

  # Create security group rules matrix
  tier_connectivity = {
    web = ["app"]
    app = ["db"]
    db  = []
  }

  # Flatten instance deployments
  instance_deployments = flatten([
    for tier, config in var.instance_configs : [
      for i in range(config.count) : {
        tier          = tier
        index         = i
        instance_type = config.instance_type
        user_data     = config.user_data
        subnet        = local.subnets_by_tier[tier][i % length(local.subnets_by_tier[tier])].name
      }
    ]
  ])

  # Create deployment map
  deployment_map = {
    for deployment in local.instance_deployments :
    "${deployment.tier}-${deployment.index}" =&gt; deployment
  }

  # Calculate network statistics
  network_stats = {
    total_subnets    = length(var.subnet_configurations)
    public_subnets   = length(local.public_subnets)
    private_subnets  = length(local.private_subnets)
    tiers            = length(local.all_tiers)
    total_instances  = sum([for c in var.instance_configs : c.count])
    azs_used         = length(var.availability_zones)
  }

  # Merge tags for each tier
  tier_tags = {
    for tier in local.all_tiers :
    tier =&gt; merge(
      var.common_tags,
      { Tier = tier }
    )
  }
}
</code></pre>
<h3 id="heading-step-4-create-maintfhttpmaintf"><strong>Step 4: Create</strong> <a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-plaintext"># main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = merge(var.common_tags, {
    Name = "${var.project_name}-vpc"
  })
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = merge(var.common_tags, {
    Name = "${var.project_name}-igw"
  })
}

# Subnets using for_each
resource "aws_subnet" "subnets" {
  for_each = local.subnet_map

  vpc_id                  = aws_vpc.main.id
  cidr_block              = each.value.cidr
  availability_zone       = local.subnet_az_mapping[each.key]
  map_public_ip_on_launch = each.value.type == "public"

  tags = merge(local.tier_tags[each.value.tier], {
    Name = "${var.project_name}-${each.key}"
    Type = each.value.type
  })
}

# Route Table for Public Subnets
resource "aws_route_table" "public" {
  count = length(local.public_subnets) &gt; 0 ? 1 : 0

  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = merge(var.common_tags, {
    Name = "${var.project_name}-public-rt"
  })
}

# Route Table Associations
resource "aws_route_table_association" "public" {
  for_each = {
    for subnet in local.public_subnets :
    subnet.name =&gt; aws_subnet.subnets[subnet.name].id
  }

  subnet_id      = each.value
  route_table_id = aws_route_table.public[0].id
}

# Security Groups per tier
resource "aws_security_group" "tier_sgs" {
  for_each = toset(local.all_tiers)

  name        = "${var.project_name}-${each.key}-sg"
  description = "Security group for ${each.key} tier"
  vpc_id      = aws_vpc.main.id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(local.tier_tags[each.key], {
    Name = "${var.project_name}-${each.key}-sg"
  })
}

# AMI Data Source
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# EC2 Instances
resource "aws_instance" "instances" {
  for_each = local.deployment_map

  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = each.value.instance_type
  subnet_id              = aws_subnet.subnets[each.value.subnet].id
  vpc_security_group_ids = [aws_security_group.tier_sgs[each.value.tier].id]
  user_data              = each.value.user_data

  tags = merge(local.tier_tags[each.value.tier], {
    Name  = "${var.project_name}-${each.key}"
    Index = each.value.index
  })
}
</code></pre>
<h3 id="heading-step-5-create-outputstfhttpoutputstf"><strong>Step 5: Create</strong> <a target="_blank" href="http://outputs.tf"><strong>outputs.tf</strong></a></h3>
<pre><code class="lang-plaintext"># outputs.tf

output "network_statistics" {
  description = "Network statistics"
  value       = local.network_stats
}

output "subnets_by_tier" {
  description = "Subnets grouped by tier"
  value = {
    for tier, subnets in local.subnets_by_tier :
    tier =&gt; [for s in subnets : s.name]
  }
}

output "subnet_details" {
  description = "Detailed subnet information"
  value = {
    for name, subnet in aws_subnet.subnets :
    name =&gt; {
      id   = subnet.id
      cidr = subnet.cidr_block
      az   = subnet.availability_zone
    }
  }
}

output "instance_distribution" {
  description = "Instances per tier"
  value = {
    for tier in local.all_tiers :
    tier =&gt; length([
      for key, inst in local.deployment_map :
      inst if inst.tier == tier
    ])
  }
}

output "instance_ips_by_tier" {
  description = "Instance IPs grouped by tier"
  value = {
    for tier in local.all_tiers :
    tier =&gt; [
      for key, instance in aws_instance.instances :
      instance.private_ip if local.deployment_map[key].tier == tier
    ]
  }
}

output "all_instance_ips" {
  description = "All instance IPs"
  value       = values(aws_instance.instances)[*].private_ip
}

output "tier_security_groups" {
  description = "Security group IDs per tier"
  value = {
    for tier, sg in aws_security_group.tier_sgs :
    tier =&gt; sg.id
  }
}
</code></pre>
<h3 id="heading-step-6-deploy-and-explore"><strong>Step 6: Deploy and Explore</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init
<span class="hljs-comment"># Plan and review the collections magic!</span>
terraform plan
<span class="hljs-comment"># Apply</span>
terraform apply -auto-approve
<span class="hljs-comment"># View outputs</span>
terraform output network_statistics
terraform output subnets_by_tier
terraform output instance_distribution
terraform output instance_ips_by_tier
<span class="hljs-comment"># Clean up</span>
terraform destroy -auto-approve
</code></pre>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ For expressions (list, map transformations)</p>
</li>
<li><p>✅ Splat expressions for attribute extraction</p>
</li>
<li><p>✅ Essential collection functions</p>
</li>
<li><p>✅ Advanced filtering and grouping</p>
</li>
<li><p>✅ Complex data transformations</p>
</li>
<li><p>✅ Dynamic infrastructure patterns</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 12: Count and For_Each - Creating Multiple Resources</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Master count and for_each</p>
</li>
<li><p>Understand when to use each</p>
</li>
<li><p>Create dynamic resources</p>
</li>
<li><p>Manage resource indexes</p>
</li>
<li><p>Build scalable configurations</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-10-output-values-and-local-values">← Day 10: Outputs &amp; Locals</a> | <a target="_blank" href="https://stackopsdiary.site/day-12-count-and-foreach-creating-multiple-resources">Day 12: Count &amp; For_Each →</a></p>
<hr />
<p><em>Remember: Collections and transformations are the key to dynamic infrastructure!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 10: Output Values & Local Values]]></title><description><![CDATA[Welcome to Day 10! Today we’ll explore two powerful features that make Terraform configurations more organized and reusable: output values for sharing data and local values for reducing repetition.
🎯 Today’s Goals

Master output values and their use...]]></description><link>https://stackopsdiary.site/day-10-output-values-and-local-values</link><guid isPermaLink="true">https://stackopsdiary.site/day-10-output-values-and-local-values</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Thu, 16 Oct 2025 12:30:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760609353373/f378ae05-04f6-44f1-9c93-c972e0609fc5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 10! Today we’ll explore two powerful features that make Terraform configurations more organized and reusable: <strong>output values</strong> for sharing data and <strong>local values</strong> for reducing repetition.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Master output values and their uses</p>
</li>
<li><p>Learn local values and when to use them</p>
</li>
<li><p>Understand output dependencies</p>
</li>
<li><p>Share data between root and child modules</p>
</li>
<li><p>Organize complex expressions with locals</p>
</li>
</ul>
<h2 id="heading-output-values-deep-dive">📤 Output Values Deep Dive</h2>
<p><strong>Output values</strong> expose information about your infrastructure after Terraform runs. Think of them as return values from your configuration.</p>
<h3 id="heading-basic-output-syntax"><strong>Basic Output Syntax</strong></h3>
<pre><code class="lang-plaintext">output "output_name" {
  description = "Description of what this outputs"
  value       = expression
  sensitive   = false  # optional
  depends_on  = []     # optional
}
</code></pre>
<h3 id="heading-why-use-outputs"><strong>Why Use Outputs?</strong></h3>
<ol>
<li><p><strong>Display information</strong> to users after apply</p>
</li>
<li><p><strong>Share data</strong> between configurations</p>
</li>
<li><p><strong>Pass data</strong> to parent modules</p>
</li>
<li><p><strong>Feed data</strong> to other tools (CI/CD, scripts)</p>
</li>
<li><p><strong>Document</strong> infrastructure details</p>
</li>
</ol>
<h2 id="heading-output-value-examples">🎨 Output Value Examples</h2>
<h3 id="heading-simple-outputs"><strong>Simple Outputs</strong></h3>
<pre><code class="lang-plaintext">output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "instance_public_ip" {
  description = "Public IP of the instance"
  value       = aws_instance.web.public_ip
}

output "region" {
  description = "AWS region"
  value       = var.aws_region
}
</code></pre>
<h3 id="heading-list-outputs"><strong>List Outputs</strong></h3>
<pre><code class="lang-plaintext">output "subnet_ids" {
  description = "IDs of all subnets"
  value       = aws_subnet.public[*].id
}

output "availability_zones" {
  description = "Availability zones used"
  value       = aws_subnet.public[*].availability_zone
}

# Output list of maps
output "subnet_details" {
  description = "Detailed subnet information"
  value = [
    for subnet in aws_subnet.public : {
      id   = subnet.id
      cidr = subnet.cidr_block
      az   = subnet.availability_zone
    }
  ]
}
</code></pre>
<h3 id="heading-map-outputs"><strong>Map Outputs</strong></h3>
<pre><code class="lang-plaintext">output "instance_ips" {
  description = "Map of instance names to IPs"
  value = {
    for name, instance in aws_instance.servers :
    name =&gt; instance.public_ip
  }
}

output "security_group_rules" {
  description = "Security group details"
  value = {
    for sg_name, sg in aws_security_group.groups :
    sg_name =&gt; {
      id          = sg.id
      name        = sg.name
      description = sg.description
    }
  }
}
</code></pre>
<h3 id="heading-computed-outputs"><strong>Computed Outputs</strong></h3>
<pre><code class="lang-plaintext">output "website_url" {
  description = "Full URL to access the website"
  value       = "&lt;https://$&gt;{aws_instance.web.public_dns}"
}

output "connection_string" {
  description = "Database connection string"
  value       = "postgresql://${aws_db_instance.main.username}@${aws_db_instance.main.endpoint}/${aws_db_instance.main.db_name}"
}

output "total_subnet_count" {
  description = "Total number of subnets"
  value       = length(aws_subnet.public) + length(aws_subnet.private)
}
</code></pre>
<h3 id="heading-sensitive-outputs"><strong>Sensitive Outputs</strong></h3>
<pre><code class="lang-plaintext">output "db_password" {
  description = "Database password"
  value       = aws_db_instance.main.password
  sensitive   = true  # Won't show in console output
}

output "api_key" {
  description = "API key for application"
  value       = random_password.api_key.result
  sensitive   = true
}

# View sensitive outputs
# terraform output -raw db_password
</code></pre>
<h3 id="heading-conditional-outputs"><strong>Conditional Outputs</strong></h3>
<pre><code class="lang-plaintext">output "nat_gateway_ip" {
  description = "NAT Gateway public IP"
  value       = var.create_nat_gateway ? aws_nat_gateway.main[0].public_ip : "Not created"
}

output "load_balancer_dns" {
  description = "Load balancer DNS name"
  value       = var.environment == "prod" ? aws_lb.main[0].dns_name : null
}
</code></pre>
<h2 id="heading-output-dependencies">🔄 Output Dependencies</h2>
<p>Sometimes an output needs to wait for other resources:</p>
<pre><code class="lang-plaintext"># Output depends on route table association being complete
output "vpc_ready" {
  description = "VPC is fully configured"
  value       = "VPC ${aws_vpc.main.id} is ready"

  depends_on = [
    aws_route_table_association.public,
    aws_internet_gateway.main
  ]
}
</code></pre>
<h2 id="heading-local-values">📍 Local Values</h2>
<p><strong>Local values</strong> assign names to expressions, making configurations more readable and reducing repetition.</p>
<h3 id="heading-basic-local-syntax"><strong>Basic Local Syntax</strong></h3>
<pre><code class="lang-plaintext">locals {
  # Simple locals
  environment = "production"
  region      = "us-east-1"

  # Computed locals
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
    Project     = var.project_name
  }

  # String interpolation
  resource_prefix = "${var.project_name}-${var.environment}"

  # Conditional locals
  instance_type = var.environment == "prod" ? "t3.large" : "t2.micro"
}

# Reference with local.name
resource "aws_instance" "web" {
  instance_type = local.instance_type
  tags          = local.common_tags
}
</code></pre>
<h3 id="heading-when-to-use-locals"><strong>When to Use Locals</strong></h3>
<p>✅ <strong>Use locals for:</strong></p>
<ol>
<li><p><strong>Repeated expressions</strong> - DRY principle</p>
</li>
<li><p><strong>Complex calculations</strong> - Improve readability</p>
</li>
<li><p><strong>Conditional logic</strong> - Simplify resource blocks</p>
</li>
<li><p><strong>Combining variables</strong> - Create derived values</p>
</li>
<li><p><strong>Transforming data</strong> - Process variable inputs</p>
</li>
</ol>
<p>❌ <strong>Don’t use locals for:</strong></p>
<ol>
<li><p>Simple variable references</p>
</li>
<li><p>One-time expressions</p>
</li>
<li><p>Values that should be variables</p>
</li>
</ol>
<h2 id="heading-locals-vs-variables">🎯 Locals vs Variables</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Variables</td><td>Locals</td></tr>
</thead>
<tbody>
<tr>
<td>Input from users</td><td>Computed internally</td></tr>
<tr>
<td>Can have defaults</td><td>Always computed</td></tr>
<tr>
<td>Declared in <code>variable</code> blocks</td><td>Declared in <code>locals</code> blocks</td></tr>
<tr>
<td>Referenced with <code>var.</code></td><td>Referenced with <code>local.</code></td></tr>
<tr>
<td>Can be validated</td><td>No validation</td></tr>
</tbody>
</table>
</div><h2 id="heading-hands-on-lab-outputs-amp-locals">🧪 Hands-On Lab: Outputs &amp; Locals</h2>
<p>Let’s build a complete infrastructure using outputs and locals effectively!</p>
<h3 id="heading-step-1-create-project-structure"><strong>Step 1: Create Project Structure</strong></h3>
<pre><code class="lang-bash">mkdir terraform-outputs-locals
<span class="hljs-built_in">cd</span> terraform-outputs-locals
</code></pre>
<h3 id="heading-step-2-create-variablestfhttpvariablestf"><strong>Step 2: Create</strong> <a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a></h3>
<pre><code class="lang-plaintext"># variables.tf

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "project_name" {
  description = "Project name"
  type        = string
  default     = "webapp"
}

variable "environment" {
  description = "Environment (dev, staging, prod)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Must be dev, staging, or prod."
  }
}

variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  description = "Availability zones"
  type        = list(string)
  default     = ["us-east-1a", "us-east-1b"]
}

variable "create_nat_gateway" {
  description = "Create NAT gateway for private subnets"
  type        = bool
  default     = false
}

variable "enable_monitoring" {
  description = "Enable detailed monitoring"
  type        = bool
  default     = false
}
</code></pre>
<h3 id="heading-step-3-create-localstfhttplocalstf"><strong>Step 3: Create</strong> <a target="_blank" href="http://locals.tf"><strong>locals.tf</strong></a></h3>
<pre><code class="lang-plaintext"># locals.tf

locals {
  # Naming convention
  resource_prefix = "${var.project_name}-${var.environment}"

  # Common tags
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "Terraform"
    CreatedAt   = timestamp()
  }

  # Environment-specific configurations
  instance_type = {
    dev     = "t2.micro"
    staging = "t2.small"
    prod    = "t3.large"
  }

  instance_count = {
    dev     = 1
    staging = 2
    prod    = 3
  }

  # Network calculations
  public_subnet_cidrs = [
    for idx in range(length(var.availability_zones)) :
    cidrsubnet(var.vpc_cidr, 8, idx)
  ]

  private_subnet_cidrs = [
    for idx in range(length(var.availability_zones)) :
    cidrsubnet(var.vpc_cidr, 8, idx + 10)
  ]

  # Security group rules based on environment
  allowed_ssh_cidrs = var.environment == "prod" ? ["10.0.0.0/8"] : ["0.0.0.0/0"]

  # Conditional resource creation flags
  create_private_subnets = var.environment != "dev"
  enable_vpc_flow_logs   = var.environment == "prod"

  # Backup configuration
  backup_retention_days = {
    dev     = 7
    staging = 14
    prod    = 30
  }

  # Computed values
  all_subnet_cidrs = concat(local.public_subnet_cidrs, local.private_subnet_cidrs)
  total_subnets    = length(local.all_subnet_cidrs)

  # Feature flags
  features = {
    monitoring       = var.enable_monitoring
    nat_gateway      = var.create_nat_gateway
    private_subnets  = local.create_private_subnets
    flow_logs        = local.enable_vpc_flow_logs
  }
}
</code></pre>
<h3 id="heading-step-4-create-maintfhttpmaintf"><strong>Step 4: Create</strong> <a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-plaintext"># main.tf

terraform {
  required_version = "&gt;= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = local.common_tags
  }
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${local.resource_prefix}-vpc"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${local.resource_prefix}-igw"
  }
}

# Public Subnets
resource "aws_subnet" "public" {
  count = length(var.availability_zones)

  vpc_id                  = aws_vpc.main.id
  cidr_block              = local.public_subnet_cidrs[count.index]
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${local.resource_prefix}-public-${count.index + 1}"
    Type = "Public"
  }
}

# Private Subnets (only in staging and prod)
resource "aws_subnet" "private" {
  count = local.create_private_subnets ? length(var.availability_zones) : 0

  vpc_id            = aws_vpc.main.id
  cidr_block        = local.private_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${local.resource_prefix}-private-${count.index + 1}"
    Type = "Private"
  }
}

# Public Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "${local.resource_prefix}-public-rt"
  }
}

# Route Table Associations
resource "aws_route_table_association" "public" {
  count = length(aws_subnet.public)

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource "aws_security_group" "web" {
  name        = "${local.resource_prefix}-web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTPS"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = local.allowed_ssh_cidrs
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.resource_prefix}-web-sg"
  }
}

# Data source for latest AMI
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# EC2 Instances
resource "aws_instance" "web" {
  count = local.instance_count[var.environment]

  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = local.instance_type[var.environment]
  subnet_id              = aws_subnet.public[count.index % length(aws_subnet.public)].id
  vpc_security_group_ids = [aws_security_group.web.id]
  monitoring             = var.enable_monitoring

  user_data = &lt;&lt;-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "&lt;h1&gt;Instance ${count.index + 1} in ${var.environment}&lt;/h1&gt;" &gt; /var/www/html/index.html
              echo "&lt;p&gt;Deployed by Terraform&lt;/p&gt;" &gt;&gt; /var/www/html/index.html
              EOF

  tags = {
    Name  = "${local.resource_prefix}-web-${count.index + 1}"
    Tier  = "Web"
    Index = count.index + 1
  }
}
</code></pre>
<h3 id="heading-step-5-create-outputstfhttpoutputstf"><strong>Step 5: Create</strong> <a target="_blank" href="http://outputs.tf"><strong>outputs.tf</strong></a></h3>
<pre><code class="lang-plaintext"># outputs.tf

# VPC Outputs
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "vpc_cidr" {
  description = "CIDR block of the VPC"
  value       = aws_vpc.main.cidr_block
}

output "vpc_arn" {
  description = "ARN of the VPC"
  value       = aws_vpc.main.arn
}

# Network Outputs
output "public_subnet_ids" {
  description = "IDs of public subnets"
  value       = aws_subnet.public[*].id
}

output "private_subnet_ids" {
  description = "IDs of private subnets"
  value       = aws_subnet.private[*].id
}

output "all_subnet_ids" {
  description = "All subnet IDs"
  value       = concat(aws_subnet.public[*].id, aws_subnet.private[*].id)
}

# Instance Outputs
output "instance_ids" {
  description = "IDs of EC2 instances"
  value       = aws_instance.web[*].id
}

output "instance_public_ips" {
  description = "Public IPs of instances"
  value       = aws_instance.web[*].public_ip
}

output "instance_private_ips" {
  description = "Private IPs of instances"
  value       = aws_instance.web[*].private_ip
}

# Website URLs
output "website_urls" {
  description = "URLs to access instances"
  value = [
    for instance in aws_instance.web :
    "&lt;http://$&gt;{instance.public_ip}"
  ]
}

# Security Group Output
output "web_security_group_id" {
  description = "ID of web security group"
  value       = aws_security_group.web.id
}

# Environment Information
output "environment_config" {
  description = "Environment configuration summary"
  value = {
    environment    = var.environment
    region         = var.aws_region
    instance_type  = local.instance_type[var.environment]
    instance_count = local.instance_count[var.environment]
    features       = local.features
  }
}

# Computed Outputs
output "total_resources" {
  description = "Total count of resources created"
  value = {
    vpc               = 1
    subnets           = length(aws_subnet.public) + length(aws_subnet.private)
    instances         = length(aws_instance.web)
    security_groups   = 1
  }
}

# Infrastructure Summary
output "infrastructure_summary" {
  description = "Complete infrastructure summary"
  value = &lt;&lt;-EOT

    ========================================
    Infrastructure Deployment Summary
    ========================================
    Project: ${var.project_name}
    Environment: ${var.environment}
    Region: ${var.aws_region}

    VPC ID: ${aws_vpc.main.id}
    VPC CIDR: ${aws_vpc.main.cidr_block}

    Subnets:
    ${join("\\n", [for subnet in aws_subnet.public : "  - ${subnet.id} (${subnet.cidr_block})"])}

    Instances:
    ${join("\\n", [for idx, instance in aws_instance.web : "  - Instance ${idx + 1}: ${instance.public_ip}"])}

    Access URLs:
    ${join("\\n", [for instance in aws_instance.web : "  - &lt;http://$&gt;{instance.public_ip}"])}
    ========================================
  EOT
}

# Local Values Output (for debugging)
output "local_values" {
  description = "Local values for reference"
  value = {
    resource_prefix       = local.resource_prefix
    public_subnet_cidrs   = local.public_subnet_cidrs
    private_subnet_cidrs  = local.private_subnet_cidrs
    backup_retention_days = local.backup_retention_days[var.environment]
  }
}
</code></pre>
<h3 id="heading-step-6-create-terraformtfvars"><strong>Step 6: Create terraform.tfvars</strong></h3>
<pre><code class="lang-plaintext"># terraform.tfvars

aws_region         = "us-east-1"
project_name       = "myapp"
environment        = "dev"
vpc_cidr           = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b"]
create_nat_gateway = false
enable_monitoring  = false
</code></pre>
<h3 id="heading-step-7-deploy-and-test"><strong>Step 7: Deploy and Test</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init
<span class="hljs-comment"># Plan</span>
terraform plan
<span class="hljs-comment"># Apply</span>
terraform apply -auto-approve
<span class="hljs-comment"># View all outpu</span>
tsterraform output
<span class="hljs-comment"># View specific output</span>
terraform output vpc_id
<span class="hljs-comment"># View formatted summary</span>
terraform output infrastructure_summary
<span class="hljs-comment"># Get raw value for scripting</span>
PUBLIC_IP=$(terraform output -raw instance_public_ips | jq -r <span class="hljs-string">'.[0]'</span>)curl http://<span class="hljs-variable">$PUBLIC_IP</span>
</code></pre>
<h3 id="heading-step-8-test-different-environments"><strong>Step 8: Test Different Environments</strong></h3>
<p>Create <code>prod.tfvars</code>:</p>
<pre><code class="lang-plaintext">environment        = "prod"
vpc_cidr           = "172.16.0.0/16"
create_nat_gateway = true
enable_monitoring  = true
</code></pre>
<pre><code class="lang-bash">terraform plan -var-file=<span class="hljs-string">"prod.tfvars"</span>
</code></pre>
<p>Notice how locals change the configuration based on the environment!</p>
<h3 id="heading-step-9-query-outputs-programmatically"><strong>Step 9: Query Outputs Programmatically</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Get JSON output</span>
terraform output -json &gt; outputs.json
<span class="hljs-comment"># Parse with jq</span>
cat outputs.json | jq <span class="hljs-string">'.instance_public_ips.value'</span>
<span class="hljs-comment"># Use in scripts</span>
terraform output -json | jq -r <span class="hljs-string">'.website_urls.value[]'</span> | <span class="hljs-keyword">while</span> <span class="hljs-built_in">read</span> url; <span class="hljs-keyword">do</span>  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Testing <span class="hljs-variable">$url</span>"</span>  curl -I <span class="hljs-variable">$urldone</span>
</code></pre>
<h3 id="heading-step-10-clean-up"><strong>Step 10: Clean Up</strong></h3>
<pre><code class="lang-bash">terraform destroy -auto-approve
</code></pre>
<h2 id="heading-best-practices">📝 Best Practices</h2>
<h3 id="heading-outputs"><strong>Outputs</strong></h3>
<p>✅ <strong>DO:</strong></p>
<ul>
<li><p>Add descriptions to all outputs</p>
</li>
<li><p>Mark sensitive data with <code>sensitive = true</code></p>
</li>
<li><p>Group related outputs logically</p>
</li>
<li><p>Use outputs to share data between configurations</p>
</li>
<li><p>Output useful debugging information</p>
</li>
</ul>
<p>❌ <strong>DON’T:</strong></p>
<ul>
<li><p>Output unnecessary information</p>
</li>
<li><p>Forget to mark sensitive values</p>
</li>
<li><p>Use outputs for internal-only values (use locals)</p>
</li>
</ul>
<h3 id="heading-locals"><strong>Locals</strong></h3>
<p>✅ <strong>DO:</strong></p>
<ul>
<li><p>Use locals for repeated expressions</p>
</li>
<li><p>Name locals clearly and descriptively</p>
</li>
<li><p>Group related locals in <code>locals</code> blocks</p>
</li>
<li><p>Use locals for complex calculations</p>
</li>
<li><p>Document complex local expressions</p>
</li>
</ul>
<p>❌ <strong>DON’T:</strong></p>
<ul>
<li><p>Overuse locals for simple values</p>
</li>
<li><p>Create overly complex local expressions</p>
</li>
<li><p>Use locals when a variable is more appropriate</p>
</li>
</ul>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Output values and their purposes</p>
</li>
<li><p>✅ Different output types (simple, lists, maps, computed)</p>
</li>
<li><p>✅ Sensitive outputs</p>
</li>
<li><p>✅ Output dependencies</p>
</li>
<li><p>✅ Local values and when to use them</p>
</li>
<li><p>✅ Locals vs variables</p>
</li>
<li><p>✅ Organizing complex configurations</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 11: Working with Lists, Maps, and Sets</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Deep dive into collection manipulation</p>
</li>
<li><p>Master for expressions</p>
</li>
<li><p>Learn splat expressions</p>
</li>
<li><p>Use collection functions</p>
</li>
<li><p>Build dynamic configurations</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-9-input-variables-types-and-validation">← Day 9: Input Variables</a> | <a target="_blank" href="https://stackopsdiary.site/day-11-working-with-lists-maps-and-sets">Day 11: Lists, Maps &amp; Sets →</a></p>
<hr />
<p><em>Remember: Outputs share data, locals reduce repetition!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 9: Input Variables - Types & Validation]]></title><description><![CDATA[Welcome to Day 9! Today we’ll master Terraform input variables - from simple strings to complex nested objects. You’ll learn how to create type-safe, validated configurations that prevent errors before they happen.
🎯 Today’s Goals

Master all variab...]]></description><link>https://stackopsdiary.site/day-9-input-variables-types-and-validation</link><guid isPermaLink="true">https://stackopsdiary.site/day-9-input-variables-types-and-validation</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Wed, 15 Oct 2025 12:30:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760520807639/f044943c-9de1-407e-b268-c6cc812f88e6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 9! Today we’ll master Terraform input variables - from simple strings to complex nested objects. You’ll learn how to create type-safe, validated configurations that prevent errors before they happen.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Master all variable types in depth</p>
</li>
<li><p>Learn complex type constructors</p>
</li>
<li><p>Implement robust variable validation</p>
</li>
<li><p>Understand type constraints and conversion</p>
</li>
<li><p>Build production-ready variable configurations</p>
</li>
</ul>
<h2 id="heading-variable-type-system">📊 Variable Type System</h2>
<p>Terraform has a rich type system:</p>
<pre><code class="lang-plaintext">Primitive Types          Complex Types
├── string              ├── list(type)
├── number              ├── set(type)
└── bool                ├── map(type)
                        ├── object({...})
                        └── tuple([...])

Special Type
└── any (accepts anything)
</code></pre>
<h2 id="heading-primitive-types-deep-dive">🔤 Primitive Types Deep Dive</h2>
<h3 id="heading-string-type"><strong>String Type</strong></h3>
<pre><code class="lang-plaintext">variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "project_name" {
  type    = string
  default = "myproject"
}

# Multi-line strings
variable "user_data" {
  type = string
  default = &lt;&lt;-EOT
    #!/bin/bash
    echo "Hello World"
    yum update -y
  EOT
}
</code></pre>
<h3 id="heading-number-type"><strong>Number Type</strong></h3>
<pre><code class="lang-plaintext">variable "instance_count" {
  description = "Number of instances to create"
  type        = number
  default     = 3
}

variable "max_size" {
  type    = number
  default = 10
}

# Decimals are supported
variable "cpu_credits" {
  type    = number
  default = 0.5
}
</code></pre>
<h3 id="heading-bool-type"><strong>Bool Type</strong></h3>
<pre><code class="lang-plaintext">variable "enable_monitoring" {
  description = "Enable detailed monitoring"
  type        = bool
  default     = true
}

variable "is_production" {
  type    = bool
  default = false
}

# Usage in resources
resource "aws_instance" "web" {
  monitoring = var.enable_monitoring
}
</code></pre>
<h2 id="heading-collection-types">📋 Collection Types</h2>
<h3 id="heading-list-type"><strong>List Type</strong></h3>
<p>Ordered collection of values of the same type.</p>
<pre><code class="lang-plaintext">variable "availability_zones" {
  description = "List of availability zones"
  type        = list(string)
  default     = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

variable "allowed_ports" {
  description = "List of allowed ports"
  type        = list(number)
  default     = [80, 443, 8080]
}

# Access elements
resource "aws_subnet" "example" {
  availability_zone = var.availability_zones[0]  # First element
}

# Iterate over list
resource "aws_subnet" "public" {
  count             = length(var.availability_zones)
  availability_zone = var.availability_zones[count.index]
}
</code></pre>
<h3 id="heading-set-type"><strong>Set Type</strong></h3>
<p>Unordered collection of unique values.</p>
<pre><code class="lang-plaintext">variable "security_group_ids" {
  description = "Set of security group IDs"
  type        = set(string)
  default     = ["sg-12345", "sg-67890"]
}

# Sets automatically remove duplicates
# ["a", "b", "a"] becomes ["a", "b"]
</code></pre>
<h3 id="heading-map-type"><strong>Map Type</strong></h3>
<p>Collection of key-value pairs.</p>
<pre><code class="lang-plaintext">variable "instance_types" {
  description = "Instance types per environment"
  type        = map(string)
  default = {
    dev     = "t2.micro"
    staging = "t2.small"
    prod    = "t3.large"
  }
}

variable "common_tags" {
  description = "Tags to apply to all resources"
  type        = map(string)
  default = {
    Project     = "MyApp"
    ManagedBy   = "Terraform"
    Environment = "Production"
  }
}

# Access map values
resource "aws_instance" "web" {
  instance_type = var.instance_types["prod"]
  tags          = var.common_tags
}
</code></pre>
<h2 id="heading-structural-types">🏗️ Structural Types</h2>
<h3 id="heading-object-type"><strong>Object Type</strong></h3>
<p>Structured type with named attributes of different types.</p>
<pre><code class="lang-plaintext">variable "vpc_config" {
  description = "VPC configuration"
  type = object({
    cidr_block           = string
    enable_dns_hostnames = bool
    enable_dns_support   = bool
    name                 = string
  })

  default = {
    cidr_block           = "10.0.0.0/16"
    enable_dns_hostnames = true
    enable_dns_support   = true
    name                 = "main-vpc"
  }
}

# Usage
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_config.cidr_block
  enable_dns_hostnames = var.vpc_config.enable_dns_hostnames
  enable_dns_support   = var.vpc_config.enable_dns_support

  tags = {
    Name = var.vpc_config.name
  }
}
</code></pre>
<h3 id="heading-tuple-type"><strong>Tuple Type</strong></h3>
<p>Sequence with fixed length and specific types for each position.</p>
<pre><code class="lang-plaintext">variable "network_config" {
  description = "Network configuration [cidr, az, public]"
  type        = tuple([string, string, bool])
  default     = ["10.0.1.0/24", "us-east-1a", true]
}

# Access by index
locals {
  cidr_block   = var.network_config[0]
  az           = var.network_config[1]
  is_public    = var.network_config[2]
}
</code></pre>
<h2 id="heading-advanced-variable-validation">🔐 Advanced Variable Validation</h2>
<h3 id="heading-basic-validation"><strong>Basic Validation</strong></h3>
<pre><code class="lang-plaintext">variable "environment" {
  description = "Environment name"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}
</code></pre>
<h3 id="heading-multiple-validation-rules"><strong>Multiple Validation Rules</strong></h3>
<pre><code class="lang-plaintext">variable "instance_count" {
  description = "Number of instances (1-10)"
  type        = number

  validation {
    condition     = var.instance_count &gt; 0
    error_message = "Instance count must be positive."
  }

  validation {
    condition     = var.instance_count &lt;= 10
    error_message = "Instance count cannot exceed 10."
  }
}
</code></pre>
<h3 id="heading-regex-validation"><strong>Regex Validation</strong></h3>
<pre><code class="lang-plaintext">variable "bucket_name" {
  description = "S3 bucket name"
  type        = string

  validation {
    condition     = can(regex("^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$", var.bucket_name))
    error_message = "Bucket name must be 3-63 characters, lowercase alphanumeric and hyphens."
  }
}

variable "ip_address" {
  description = "IP address"
  type        = string

  validation {
    condition = can(regex(
      "^([0-9]{1,3}\\\\.){3}[0-9]{1,3}$",
      var.ip_address
    ))
    error_message = "Must be a valid IPv4 address."
  }
}

variable "email" {
  description = "Email address"
  type        = string

  validation {
    condition     = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$", var.email))
    error_message = "Must be a valid email address."
  }
}
</code></pre>
<h3 id="heading-complex-validation-logic"><strong>Complex Validation Logic</strong></h3>
<pre><code class="lang-plaintext">variable "instance_config" {
  description = "Instance configuration"
  type = object({
    type  = string
    count = number
  })

  validation {
    condition     = contains(["t2.micro", "t2.small", "t3.medium"], var.instance_config.type)
    error_message = "Instance type must be t2.micro, t2.small, or t3.medium."
  }

  validation {
    condition     = var.instance_config.count &gt;= 1 &amp;&amp; var.instance_config.count &lt;= 5
    error_message = "Instance count must be between 1 and 5."
  }
}
</code></pre>
<h3 id="heading-validation-functions"><strong>Validation Functions</strong></h3>
<pre><code class="lang-plaintext">variable "region" {
  type = string

  validation {
    # Check if region starts with valid prefix
    condition = anytrue([
      startswith(var.region, "us-"),
      startswith(var.region, "eu-"),
      startswith(var.region, "ap-"),
    ])
    error_message = "Region must start with us-, eu-, or ap-."
  }
}

variable "tags" {
  type = map(string)

  validation {
    # Ensure required tags exist
    condition     = alltrue([
      contains(keys(var.tags), "Environment"),
      contains(keys(var.tags), "Project"),
    ])
    error_message = "Tags must include Environment and Project."
  }
}
</code></pre>
<h3 id="heading-map-of-objects"><strong>Map of Objects</strong></h3>
<pre><code class="lang-plaintext">variable "applications" {
  description = "Application configurations"
  type = map(object({
    instance_type = string
    instance_count = number
    subnets        = list(string)
    enable_monitoring = bool
  }))

  default = {
    web = {
      instance_type     = "t3.medium"
      instance_count    = 3
      subnets           = ["subnet-1", "subnet-2"]
      enable_monitoring = true
    }
    api = {
      instance_type     = "t3.large"
      instance_count    = 2
      subnets           = ["subnet-3", "subnet-4"]
      enable_monitoring = true
    }
    worker = {
      instance_type     = "t3.small"
      instance_count    = 5
      subnets           = ["subnet-5"]
      enable_monitoring = false
    }
  }
}

# Usage
resource "aws_instance" "apps" {
  for_each = var.applications

  instance_type = each.value.instance_type
  # ... use other values
}
</code></pre>
<h2 id="heading-hands-on-lab-advanced-variables">🧪 Hands-On Lab: Advanced Variables</h2>
<p>Let’s build a complete infrastructure with advanced variable types and validation!</p>
<h3 id="heading-step-1-create-project"><strong>Step 1: Create Project</strong></h3>
<pre><code class="lang-bash">mkdir terraform-advanced-variables
<span class="hljs-built_in">cd</span> terraform-advanced-variables
</code></pre>
<h3 id="heading-step-2-create-variablestfhttpvariablestf"><strong>Step 2: Create</strong> <a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a></h3>
<pre><code class="lang-plaintext"># variables.tf

variable "aws_region" {
  description = "AWS region for resources"
  type        = string
  default     = "us-east-1"

  validation {
    condition = can(regex("^(us|eu|ap|sa|ca|me|af)-(north|south|east|west|central|northeast|southeast)-[1-3]$", var.aws_region))
    error_message = "Must be a valid AWS region."
  }
}

variable "environment" {
  description = "Environment name"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "vpc_config" {
  description = "VPC configuration"
  type = object({
    cidr_block           = string
    enable_dns_hostnames = bool
    enable_dns_support   = bool
    enable_nat_gateway   = bool
  })

  validation {
    condition     = can(cidrhost(var.vpc_config.cidr_block, 0))
    error_message = "VPC CIDR block must be valid."
  }
}

variable "subnets" {
  description = "Subnet configurations"
  type = list(object({
    name              = string
    cidr_block        = string
    availability_zone = string
    type              = string  # "public" or "private"
  }))

  validation {
    condition = alltrue([
      for subnet in var.subnets :
      contains(["public", "private"], subnet.type)
    ])
    error_message = "Subnet type must be either 'public' or 'private'."
  }

  validation {
    condition = alltrue([
      for subnet in var.subnets :
      can(cidrhost(subnet.cidr_block, 0))
    ])
    error_message = "All subnet CIDR blocks must be valid."
  }
}

variable "security_groups" {
  description = "Security group configurations"
  type = map(object({
    description = string
    ingress_rules = list(object({
      from_port   = number
      to_port     = number
      protocol    = string
      cidr_blocks = list(string)
      description = string
    }))
  }))
}

variable "instance_configs" {
  description = "EC2 instance configurations"
  type = map(object({
    instance_type = string
    count         = number
    subnet_type   = string
    security_groups = list(string)
  }))

  validation {
    condition = alltrue([
      for key, config in var.instance_configs :
      config.count &gt;= 0 &amp;&amp; config.count &lt;= 10
    ])
    error_message = "Instance count must be between 0 and 10."
  }
}

variable "tags" {
  description = "Common tags for all resources"
  type        = map(string)

  validation {
    condition = alltrue([
      contains(keys(var.tags), "Project"),
      contains(keys(var.tags), "Owner"),
    ])
    error_message = "Tags must include Project and Owner."
  }
}

variable "enable_backup" {
  description = "Enable automated backups"
  type        = bool
  default     = true
}
</code></pre>
<h3 id="heading-step-3-create-terraformtfvars"><strong>Step 3: Create terraform.tfvars</strong></h3>
<pre><code class="lang-plaintext"># terraform.tfvars

aws_region  = "us-east-1"
environment = "dev"

vpc_config = {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  enable_nat_gateway   = false
}

subnets = [
  {
    name              = "public-subnet-1"
    cidr_block        = "10.0.1.0/24"
    availability_zone = "us-east-1a"
    type              = "public"
  },
  {
    name              = "public-subnet-2"
    cidr_block        = "10.0.2.0/24"
    availability_zone = "us-east-1b"
    type              = "public"
  },
  {
    name              = "private-subnet-1"
    cidr_block        = "10.0.10.0/24"
    availability_zone = "us-east-1a"
    type              = "private"
  }
]

security_groups = {
  web = {
    description = "Security group for web servers"
    ingress_rules = [
      {
        from_port   = 80
        to_port     = 80
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
        description = "HTTP from anywhere"
      },
      {
        from_port   = 443
        to_port     = 443
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
        description = "HTTPS from anywhere"
      }
    ]
  }
  app = {
    description = "Security group for application servers"
    ingress_rules = [
      {
        from_port   = 8080
        to_port     = 8080
        protocol    = "tcp"
        cidr_blocks = ["10.0.0.0/16"]
        description = "App port from VPC"
      }
    ]
  }
}

instance_configs = {
  web = {
    instance_type   = "t2.micro"
    count           = 2
    subnet_type     = "public"
    security_groups = ["web"]
  }
  app = {
    instance_type   = "t2.small"
    count           = 2
    subnet_type     = "private"
    security_groups = ["app"]
  }
}

tags = {
  Project     = "AdvancedVariables"
  Owner       = "DevOps Team"
  Environment = "Development"
  ManagedBy   = "Terraform"
}

enable_backup = true
</code></pre>
<h3 id="heading-step-4-create-maintfhttpmaintf"><strong>Step 4: Create</strong> <a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-plaintext"># main.tf

terraform {
  required_version = "&gt;= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = var.tags
  }
}

# VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_config.cidr_block
  enable_dns_hostnames = var.vpc_config.enable_dns_hostnames
  enable_dns_support   = var.vpc_config.enable_dns_support

  tags = {
    Name = "${var.environment}-vpc"
  }
}

# Internet Gateway (for public subnets)
resource "aws_internet_gateway" "main" {
  count = length([for s in var.subnets : s if s.type == "public"]) &gt; 0 ? 1 : 0

  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.environment}-igw"
  }
}

# Subnets
resource "aws_subnet" "subnets" {
  for_each = { for idx, subnet in var.subnets : subnet.name =&gt; subnet }

  vpc_id                  = aws_vpc.main.id
  cidr_block              = each.value.cidr_block
  availability_zone       = each.value.availability_zone
  map_public_ip_on_launch = each.value.type == "public"

  tags = {
    Name = each.value.name
    Type = each.value.type
  }
}

# Security Groups
resource "aws_security_group" "groups" {
  for_each = var.security_groups

  name        = "${var.environment}-${each.key}-sg"
  description = each.value.description
  vpc_id      = aws_vpc.main.id

  dynamic "ingress" {
    for_each = each.value.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      description = ingress.value.description
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.environment}-${each.key}-sg"
  }
}
</code></pre>
<h3 id="heading-step-5-test-validation"><strong>Step 5: Test Validation</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize</span>
terraform init
<span class="hljs-comment"># Test with invalid environment</span>
terraform plan -var=<span class="hljs-string">"environment=production"</span>
<span class="hljs-comment"># Error: Environment must be dev, staging, or prod.</span>
<span class="hljs-comment"># Test with invalid region</span>
terraform plan -var=<span class="hljs-string">"aws_region=invalid-region"</span>
<span class="hljs-comment"># Error: Must be a valid AWS region.</span>
<span class="hljs-comment"># Test with valid values</span>
terraform plan
</code></pre>
<h3 id="heading-step-6-test-different-environments"><strong>Step 6: Test Different Environments</strong></h3>
<p>Create <code>prod.tfvars</code>:</p>
<pre><code class="lang-plaintext">environment = "prod"

vpc_config = {
  cidr_block           = "172.16.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  enable_nat_gateway   = true
}

instance_configs = {
  web = {
    instance_type   = "t3.medium"
    count           = 4
    subnet_type     = "public"
    security_groups = ["web"]
  }
}
</code></pre>
<pre><code class="lang-bash">terraform plan -var-file=<span class="hljs-string">"prod.tfvars"</span>
</code></pre>
<h3 id="heading-step-7-apply-and-clean-up"><strong>Step 7: Apply and Clean Up</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Apply</span>
terraform apply -auto-approve

<span class="hljs-comment"># Destroy</span>
terraform destroy -auto-approve
</code></pre>
<h2 id="heading-variable-best-practices">📝 Variable Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Always specify types</strong></p>
</li>
<li><p><strong>Add descriptions</strong></p>
</li>
<li><p><strong>Use validation for critical values</strong></p>
</li>
<li><p><strong>Provide sensible defaults</strong></p>
</li>
<li><p><strong>Use complex types when appropriate</strong></p>
</li>
<li><p><strong>Document validation rules</strong></p>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p><strong>Don’t use</strong> <code>any</code> type unless necessary</p>
</li>
<li><p><strong>Don’t skip validation for user input</strong></p>
</li>
<li><p><strong>Don’t make everything a variable</strong></p>
</li>
<li><p><strong>Don’t use overly complex nested types</strong></p>
</li>
</ol>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ All Terraform variable types</p>
</li>
<li><p>✅ Complex type constructors (list, map, object, tuple)</p>
</li>
<li><p>✅ Advanced validation techniques</p>
</li>
<li><p>✅ Regex and custom validation</p>
</li>
<li><p>✅ Nested and complex types</p>
</li>
<li><p>✅ Type-safe configurations</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 10: Output Values &amp; Local Values</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Master output values</p>
</li>
<li><p>Learn local values and when to use them</p>
</li>
<li><p>Understand output dependencies</p>
</li>
<li><p>Share data between configurations</p>
</li>
<li><p>Build modular outputs</p>
</li>
</ul>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-8-terraform-cli-commands-deep-dive">← Day 8: <strong>Terraform CLI</strong></a> | <a target="_blank" href="https://stackopsdiary.site/day-10-output-values-and-local-values">Day 10: Outputs &amp; Locals →</a></p>
<hr />
<p><em>Remember: Type safety and validation prevent errors before they happen!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 8: Terraform CLI Commands Deep Dive]]></title><description><![CDATA[Welcome to Week 2! Last week you learned the fundamentals. This week we’ll dive deeper into Terraform’s powerful features. Today, we’ll master the Terraform CLI - your primary tool for infrastructure management.
🎯 Today’s Goals

Master all essential...]]></description><link>https://stackopsdiary.site/day-8-terraform-cli-commands-deep-dive</link><guid isPermaLink="true">https://stackopsdiary.site/day-8-terraform-cli-commands-deep-dive</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Tue, 14 Oct 2025 12:30:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760435728817/9fbfece1-d720-4ddf-b4cc-fc3adc20f97b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Week 2! Last week you learned the fundamentals. This week we’ll dive deeper into Terraform’s powerful features. Today, we’ll master the Terraform CLI - your primary tool for infrastructure management.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Master all essential Terraform CLI commands</p>
</li>
<li><p>Learn command options and flags</p>
</li>
<li><p>Understand command workflows</p>
</li>
<li><p>Practice debugging and troubleshooting</p>
</li>
<li><p>Explore advanced CLI features</p>
</li>
</ul>
<h2 id="heading-terraform-cli-structure">🔧 Terraform CLI Structure</h2>
<pre><code class="lang-plaintext">terraform &lt;command&gt; [options] [arguments]

Example:
terraform apply -auto-approve -var="region=us-west-2"
          │      │            │
       Command  Options    Arguments
</code></pre>
<h2 id="heading-essential-commands-reference">📚 Essential Commands Reference</h2>
<h3 id="heading-1-init-initialize-working-directory"><strong>1. init - Initialize Working Directory</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Basic initialization</span>
terraform init

<span class="hljs-comment"># Upgrade providers</span>
terraform init -upgrade

<span class="hljs-comment"># Reconfigure backend</span>
terraform init -reconfigure

<span class="hljs-comment"># Copy from existing backend</span>
terraform init -migrate-state

<span class="hljs-comment"># Skip provider plugins</span>
terraform init -backend=<span class="hljs-literal">false</span>
</code></pre>
<p><strong>What init does:</strong></p>
<ul>
<li><p>Downloads provider plugins</p>
</li>
<li><p>Initializes backend</p>
</li>
<li><p>Creates <code>.terraform</code> directory</p>
</li>
<li><p>Generates lock file</p>
</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash">terraform init -upgrade -backend-config=<span class="hljs-string">"bucket=my-state-bucket"</span>
</code></pre>
<h3 id="heading-2-plan-preview-changes"><strong>2. plan - Preview Changes</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Basic plan</span>
terraform plan

<span class="hljs-comment"># Save plan to file</span>
terraform plan -out=tfplan

<span class="hljs-comment"># Show resource changes</span>
terraform plan -detailed-exitcode

<span class="hljs-comment"># Target specific resources</span>
terraform plan -target=aws_instance.web

<span class="hljs-comment"># Refresh state before planning</span>
terraform plan -refresh=<span class="hljs-literal">true</span>

<span class="hljs-comment"># Variable passing</span>
terraform plan -var=<span class="hljs-string">"instance_type=t2.small"</span>
terraform plan -var-file=<span class="hljs-string">"production.tfvars"</span>
</code></pre>
<p><strong>Exit codes:</strong></p>
<ul>
<li><p>0 = No changes</p>
</li>
<li><p>1 = Error</p>
</li>
<li><p>2 = Successful plan with changes</p>
</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash">terraform plan -out=production.tfplan -var-file=<span class="hljs-string">"prod.tfvars"</span>
</code></pre>
<h3 id="heading-3-apply-execute-changes"><strong>3. apply - Execute Changes</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Interactive apply (prompts for confirmation)</span>
terraform apply

<span class="hljs-comment"># Auto-approve (skip confirmation)</span>
terraform apply -auto-approve

<span class="hljs-comment"># Apply saved plan</span>
terraform apply tfplan

<span class="hljs-comment"># Target specific resource</span>
terraform apply -target=aws_instance.web

<span class="hljs-comment"># Parallelism control</span>
terraform apply -parallelism=10

<span class="hljs-comment"># Replace specific resource</span>
terraform apply -replace=aws_instance.web
</code></pre>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash">terraform apply -auto-approve -var=<span class="hljs-string">"environment=production"</span>
</code></pre>
<h3 id="heading-4-destroy-delete-infrastructure"><strong>4. destroy - Delete Infrastructure</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Interactive destroy</span>
terraform destroy

<span class="hljs-comment"># Auto-approve</span>
terraform destroy -auto-approve

<span class="hljs-comment"># Destroy specific resource</span>
terraform destroy -target=aws_instance.web

<span class="hljs-comment"># Destroy with variables</span>
terraform destroy -var-file=<span class="hljs-string">"dev.tfvars"</span>
</code></pre>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash">terraform destroy -target=aws_s3_bucket.temp -auto-approve
</code></pre>
<h3 id="heading-5-validate-check-configuration"><strong>5. validate - Check Configuration</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Validate syntax</span>
terraform validate

<span class="hljs-comment"># JSON output</span>
terraform validate -json
</code></pre>
<p><strong>Checks for:</strong></p>
<ul>
<li><p>Syntax errors</p>
</li>
<li><p>Invalid resource references</p>
</li>
<li><p>Missing required arguments</p>
</li>
</ul>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash">terraform validate &amp;&amp; <span class="hljs-built_in">echo</span> <span class="hljs-string">"Configuration is valid!"</span>
</code></pre>
<h3 id="heading-6-fmt-format-code"><strong>6. fmt - Format Code</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Format current directory</span>
terraform fmt

<span class="hljs-comment"># Format recursively</span>
terraform fmt -recursive

<span class="hljs-comment"># Check if formatting is needed</span>
terraform fmt -check

<span class="hljs-comment"># Show diff</span>
terraform fmt -diff

<span class="hljs-comment"># Write to specific file</span>
terraform fmt main.tf
</code></pre>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash">terraform fmt -recursive -diff
</code></pre>
<h3 id="heading-7-show-display-state-or-plan"><strong>7. show - Display State or Plan</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Show current state</span>
terraform show

<span class="hljs-comment"># Show specific resource</span>
terraform state show aws_instance.web
</code></pre>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash">terraform show -json | jq <span class="hljs-string">'.values.root_module.resources'</span>
</code></pre>
<h3 id="heading-8-output-display-outputs"><strong>8. output - Display Outputs</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Show all outputs</span>
terraform output

<span class="hljs-comment"># Specific output</span>
terraform output vpc_id

<span class="hljs-comment"># Raw output (no quotes)</span>
terraform output -raw public_ip

<span class="hljs-comment"># JSON format</span>
terraform output -json
</code></pre>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Use output in scripts</span>
PUBLIC_IP=$(terraform output -raw instance_public_ip)curl http://<span class="hljs-variable">$PUBLIC_IP</span>
</code></pre>
<h3 id="heading-10-import-import-existing-resources"><strong>10. import - Import Existing Resources</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Import resource into state</span>
terraform import aws_instance.web i-1234567890abcdef0

<span class="hljs-comment"># Import with variable</span>
terraform import -var-file=<span class="hljs-string">"prod.tfvars"</span> aws_vpc.main vpc-12345
</code></pre>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># First, write the resource blockresource "aws_instance" "existing" {</span>
  <span class="hljs-comment"># ... configuration}</span>

<span class="hljs-comment"># Then import</span>
terraform import aws_instance.existing i-1234567890abcdef0
</code></pre>
<h2 id="heading-state-management-commands">🗂️ State Management Commands</h2>
<h3 id="heading-11-state-list-list-resources"><strong>11. state list - List Resources</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List all resources</span>
terraform state list

<span class="hljs-comment"># Filter by pattern</span>
terraform state list | grep aws_instance

<span class="hljs-comment"># List module resources</span>
terraform state list module.vpc
</code></pre>
<h3 id="heading-12-state-show-display-resource"><strong>12. state show - Display Resource</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Show resource details</span>
terraform state show aws_instance.web

<span class="hljs-comment"># With address</span>
terraform state show <span class="hljs-string">'aws_instance.web[0]'</span>
</code></pre>
<h3 id="heading-13-state-mv-move-resources"><strong>13. state mv - Move Resources</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Rename resource</span>
terraform state mv aws_instance.web aws_instance.webserver

<span class="hljs-comment"># Move to module</span>
terraform state mv aws_instance.web module.compute.aws_instance.web

<span class="hljs-comment"># Move from module</span>
terraform state mv module.old.aws_instance.web aws_instance.web
</code></pre>
<h3 id="heading-14-state-rm-remove-resources"><strong>14. state rm - Remove Resources</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Remove from state (doesn't delete resource)</span>
terraform state rm aws_instance.web

<span class="hljs-comment"># Remove multiple</span>
terraform state rm aws_instance.web aws_instance.db

<span class="hljs-comment"># Remove with index</span>
terraform state rm <span class="hljs-string">'aws_subnet.public[1]'</span>
</code></pre>
<h3 id="heading-15-state-pullpush"><strong>15. state pull/push</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Download remote state</span>
terraform state pull &gt; backup.tfstate

<span class="hljs-comment"># Upload state (dangerous!)</span>
terraform state push backup.tfstate
</code></pre>
<h3 id="heading-16-force-unlock-remove-lock"><strong>16. force-unlock - Remove Lock</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Unlock stuck state</span>
terraform force-unlock LOCK_ID
</code></pre>
<h2 id="heading-workspace-commands">📊 Workspace Commands</h2>
<pre><code class="lang-bash"><span class="hljs-comment"># List workspaces</span>
terraform workspace list

<span class="hljs-comment"># Create workspace</span>
terraform workspace new dev

<span class="hljs-comment"># Switch workspace</span>
terraform workspace select prod

<span class="hljs-comment"># Show current workspace</span>
terraform workspace show

<span class="hljs-comment"># Delete workspace</span>
terraform workspace delete dev
</code></pre>
<h2 id="heading-advanced-commands">🔍 Advanced Commands</h2>
<h3 id="heading-17-graph-visualize-dependencies"><strong>17. graph - Visualize Dependencies</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Generate dependency graph</span>
terraform graph

<span class="hljs-comment"># For planned changes</span>
terraform graph -<span class="hljs-built_in">type</span>=plan

<span class="hljs-comment"># For destroy operation</span>
terraform graph -<span class="hljs-built_in">type</span>=plan-destroy

<span class="hljs-comment"># Visualize with Graphviz</span>
terraform graph | dot -Tpng &gt; graph.png
</code></pre>
<h3 id="heading-18-providers-show-providers"><strong>18. providers - Show Providers</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List providers</span>
terraform providers

<span class="hljs-comment"># Show provider schema</span>
terraform providers schema

<span class="hljs-comment"># Mirror providers locally</span>
terraform providers mirror ./providers

<span class="hljs-comment"># Lock providers</span>
terraform providers lock
</code></pre>
<h2 id="heading-hands-on-lab-cli-commands-mastery">🧪 Hands-On Lab: CLI Commands Mastery</h2>
<p>Let’s practice all these commands!</p>
<h3 id="heading-step-1-create-project"><strong>Step 1: Create Project</strong></h3>
<pre><code class="lang-bash">mkdir terraform-cli-lab
<span class="hljs-built_in">cd</span> terraform-cli-lab
</code></pre>
<h3 id="heading-step-2-create-configuration"><strong>Step 2: Create Configuration</strong></h3>
<p><code>main.tf</code>:</p>
<pre><code class="lang-plaintext">terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&gt; 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "instance_count" {
  description = "Number of instances"
  type        = number
  default     = 2
}

# VPC
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name        = "cli-lab-vpc-${var.environment}"
    Environment = var.environment
  }
}

# Subnets
resource "aws_subnet" "public" {
  count = var.instance_count

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "public-subnet-${count.index + 1}"
  }
}

# Data source
data "aws_availability_zones" "available" {
  state = "available"
}

# Outputs
output "vpc_id" {
  value = aws_vpc.main.id
}

output "subnet_ids" {
  value = aws_subnet.public[*].id
}

output "region" {
  value = var.region
}
</code></pre>
<h3 id="heading-step-3-practice-commands"><strong>Step 3: Practice Commands</strong></h3>
<h3 id="heading-initialization"><strong>Initialization</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initializeterraform init</span>
<span class="hljs-comment"># Check what was createdls -la .terraform/</span>
cat .terraform.lock.hcl
</code></pre>
<h3 id="heading-validation-and-formatting"><strong>Validation and Formatting</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Check format</span>
terraform fmt -check

<span class="hljs-comment"># Format files</span>
terraform fmt

<span class="hljs-comment"># Validate</span>
terraform validate
</code></pre>
<h3 id="heading-planning"><strong>Planning</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Basic plan</span>
terraform plan

<span class="hljs-comment"># Plan with variables</span>
terraform plan -var=<span class="hljs-string">"environment=staging"</span>

<span class="hljs-comment"># Save plan</span>
terraform plan -out=dev.tfplan

<span class="hljs-comment"># View saved plan</span>
terraform show dev.tfplan
</code></pre>
<h3 id="heading-apply"><strong>Apply</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Apply saved plan</span>
terraform apply dev.tfplan

<span class="hljs-comment"># Direct apply with auto-approve</span>
terraform apply -auto-approve -var=<span class="hljs-string">"instance_count=3"</span>
</code></pre>
<h3 id="heading-state-operations"><strong>State Operations</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List all resources</span>
terraform state list

<span class="hljs-comment"># Show specific resource</span>
terraform state show aws_vpc.main

<span class="hljs-comment"># Show subnet with index</span>
terraform state show <span class="hljs-string">'aws_subnet.public[0]'</span>
</code></pre>
<h3 id="heading-outputs"><strong>Outputs</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># All outputs</span>
terraform output

<span class="hljs-comment"># Specific output</span>
terraform output vpc_id

<span class="hljs-comment"># Raw output for scripting</span>
terraform output -raw region

<span class="hljs-comment"># JSON output</span>
terraform output -json | jq
</code></pre>
<h3 id="heading-graph-visualization"><strong>Graph Visualization</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Generate graph</span>
terraform graph &gt; graph.dot

<span class="hljs-comment"># If you have Graphviz installed:</span>
terraform graph | dot -Tpng &gt; infrastructure.png
</code></pre>
<h3 id="heading-targeted-operations"><strong>Targeted Operations</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Plan specific resource</span>
terraform plan -target=aws_subnet.public[0]

<span class="hljs-comment"># Apply to specific resource</span>
terraform apply -target=aws_vpc.main -auto-approve

<span class="hljs-comment"># Destroy specific resource</span>
terraform destroy -target=<span class="hljs-string">'aws_subnet.public[1]'</span> -auto-approve
</code></pre>
<h3 id="heading-step-4-practice-state-manipulation"><strong>Step 4: Practice State Manipulation</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Pull state for backup</span>
terraform state pull &gt; backup.tfstate

<span class="hljs-comment"># List resources</span>
terraform state list

<span class="hljs-comment"># Move resource (rename)</span>
terraform state mv <span class="hljs-string">'aws_subnet.public[0]'</span> aws_subnet.primary

<span class="hljs-comment"># Show renamed resource</span>
terraform state show aws_subnet.primary

<span class="hljs-comment"># Move it back</span>
terraform state mv aws_subnet.primary <span class="hljs-string">'aws_subnet.public[0]'</span>
</code></pre>
<h3 id="heading-step-5-workspaces"><strong>Step 5: Workspaces</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List workspaces</span>
terraform workspace list

<span class="hljs-comment"># Create new workspace</span>
terraform workspace new staging

<span class="hljs-comment"># Switch workspaces</span>
terraform workspace select default
terraform workspace select staging

<span class="hljs-comment"># Deploy to staging</span>
terraform apply -auto-approve -var=<span class="hljs-string">"environment=staging"</span>

<span class="hljs-comment"># Switch back to default</span>
terraform workspace select default

<span class="hljs-comment"># Delete staging workspace</span>
terraform destroy -auto-approve  

<span class="hljs-comment"># In staging workspace first</span>
terraform workspace select default
terraform workspace delete staging
</code></pre>
<h3 id="heading-step-6-cleanup"><strong>Step 6: Cleanup</strong></h3>
<pre><code class="lang-bash">terraform destroy -auto-approve
</code></pre>
<h2 id="heading-command-cheat-sheet">🎯 Command Cheat Sheet</h2>
<h3 id="heading-daily-commands"><strong>Daily Commands</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize project</span>
terraform init 

<span class="hljs-comment"># Format code</span>
terraform fmt

<span class="hljs-comment"># Check syntax</span>
terraform validate

<span class="hljs-comment"># Preview changes            </span>
terraform plan              

<span class="hljs-comment"># Apply changes</span>
terraform apply    

<span class="hljs-comment"># Show outputs  </span>
terraform output   

<span class="hljs-comment"># Delete everything</span>
terraform destroy
</code></pre>
<h3 id="heading-advanced"><strong>Advanced</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Import existing</span>
terraform import               

<span class="hljs-comment"># Manage workspaces</span>
terraform workspace            

<span class="hljs-comment"># Manage providers</span>
terraform providers

<span class="hljs-comment"># Unlock state          </span>
terraform force-unlock
</code></pre>
<h2 id="heading-cli-best-practices">📝 CLI Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Always run plan before apply</strong></p>
<pre><code class="lang-bash"> terraform plan &amp;&amp; terraform apply
</code></pre>
</li>
<li><p><strong>Save plans for review</strong></p>
<pre><code class="lang-bash"> terraform plan -out=plan.tfplan

 <span class="hljs-comment"># Review the plan</span>
 terraform apply plan.tfplan
</code></pre>
</li>
<li><p><strong>Use auto-approve only in automation</strong></p>
<pre><code class="lang-bash"> <span class="hljs-comment"># In CI/CD</span>
 terraform apply -auto-approve
</code></pre>
</li>
<li><p><strong>Format before committing</strong></p>
<pre><code class="lang-bash"> terraform fmt -recursive
 git add .
 git commit -m <span class="hljs-string">"Formatted Terraform files"</span>
</code></pre>
</li>
<li><p><strong>Validate regularly</strong></p>
<pre><code class="lang-bash"> terraform validate
</code></pre>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p>Don’t skip planning in production</p>
</li>
<li><p>Don’t use <code>auto-approve</code> interactively</p>
</li>
<li><p>Don’t forget to backup before state operations</p>
</li>
<li><p>Don’t use <code>state push</code> unless absolutely necessary</p>
</li>
</ol>
<h2 id="heading-debugging-commands">🐛 Debugging Commands</h2>
<pre><code class="lang-bash"><span class="hljs-comment"># Enable debug logging</span>
<span class="hljs-built_in">export</span> TF_LOG=DEBUG
terraform apply

<span class="hljs-comment"># Log to file</span>
<span class="hljs-built_in">export</span> TF_LOG=TRACE
<span class="hljs-built_in">export</span> TF_LOG_PATH=./terraform.log

terraform apply

<span class="hljs-comment"># Disable logging</span>
<span class="hljs-built_in">unset</span> TF_LOG
<span class="hljs-built_in">unset</span> TF_LOG_PATH

<span class="hljs-comment"># Crash logs</span>
cat crash.log
</code></pre>
<p><strong>Log Levels:</strong></p>
<ul>
<li><p><code>TRACE</code> - Most verbose</p>
</li>
<li><p><code>DEBUG</code> - Debug information</p>
</li>
<li><p><code>INFO</code> - General information</p>
</li>
<li><p><code>WARN</code> - Warnings</p>
</li>
<li><p><code>ERROR</code> - Errors only</p>
</li>
</ul>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ All essential Terraform CLI commands</p>
</li>
<li><p>✅ Command options and flags</p>
</li>
<li><p>✅ State management commands</p>
</li>
<li><p>✅ Workspace commands</p>
</li>
<li><p>✅ Debugging and troubleshooting</p>
</li>
<li><p>✅ CLI best practices</p>
</li>
<li><p>✅ Command workflows</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 9: Input Variables - Types &amp; Validation</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Deep dive into variable types</p>
</li>
<li><p>Master complex variable structures</p>
</li>
<li><p>Learn advanced validation rules</p>
</li>
<li><p>Work with variable precedence</p>
</li>
<li><p>Build type-safe configurations</p>
</li>
</ul>
<h2 id="heading-challenge-exercise">💭 Challenge Exercise</h2>
<p>Create a script that:</p>
<ol>
<li><p>Runs <code>terraform plan</code> and saves to file</p>
</li>
<li><p>Checks exit code</p>
</li>
<li><p>If changes exist (exit code 2), shows the plan</p>
</li>
<li><p>Asks for confirmation before applying</p>
</li>
<li><p>Backs up state after apply</p>
</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
terraform plan -out=plan.tfplan -detailed-exitcode
EXIT_CODE=$?
<span class="hljs-keyword">if</span> [ <span class="hljs-variable">$EXIT_CODE</span> -eq 2 ];
<span class="hljs-keyword">then</span>  terraform show plan.tfplan
  <span class="hljs-built_in">read</span> -p <span class="hljs-string">"Apply changes? (yes/no): "</span> CONFIRM  
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$CONFIRM</span>"</span> = <span class="hljs-string">"yes"</span> ]; 
    <span class="hljs-keyword">then</span> terraform state pull &gt; backup.tfstate
    terraform apply plan.tfplan
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">fi</span>
</code></pre>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-6-state-management-fundamentals">← Day 6: <strong>State Management</strong></a> | <a target="_blank" href="https://stackopsdiary.site/day-9-input-variables-types-and-validation">Day 9: Input Variables Deep Dive →</a></p>
<hr />
<p><em>Remember: The CLI is your primary interface to Terraform. Master it!</em></p>
]]></content:encoded></item><item><title><![CDATA[Thank You]]></title><description><![CDATA[Thanks For Reading, Follow Me For More
Have a great day!..]]></description><link>https://stackopsdiary.site/thank-you</link><guid isPermaLink="true">https://stackopsdiary.site/thank-you</guid><category><![CDATA[Testing]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Sat, 11 Oct 2025 14:29:25 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong><em>Thanks For Reading, Follow Me For More</em></strong></p>
<p>Have a great day!..</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Day 6: State Management Fundamentals]]></title><description><![CDATA[Welcome to Day 6 - the final day of Week 1! Today we’ll dive deep into Terraform state, one of the most critical concepts in Terraform. Understanding state management is essential for working with Terraform professionally.
🎯 Today’s Goals

Understan...]]></description><link>https://stackopsdiary.site/day-6-state-management-fundamentals</link><guid isPermaLink="true">https://stackopsdiary.site/day-6-state-management-fundamentals</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[Cloud Computing]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Sat, 11 Oct 2025 12:30:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760164926897/c4110698-e11d-4b29-bae0-91cba6a63ddf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 6 - the final day of Week 1! Today we’ll dive deep into <strong>Terraform state</strong>, one of the most critical concepts in Terraform. Understanding state management is essential for working with Terraform professionally.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Understand what Terraform state is and why it matters</p>
</li>
<li><p>Learn state file structure and contents</p>
</li>
<li><p>Master state commands</p>
</li>
<li><p>Configure remote state backends</p>
</li>
<li><p>Implement S3 backend with state locking</p>
</li>
<li><p>Learn state best practices and security</p>
</li>
</ul>
<h2 id="heading-what-is-terraform-state">📦 What is Terraform State?</h2>
<p><strong>Terraform state</strong> is a JSON file that maps your configuration to real-world resources. It’s Terraform’s memory of what infrastructure exists.</p>
<h3 id="heading-why-state-exists"><strong>Why State Exists</strong></h3>
<pre><code class="lang-json">Your Code      |    State File         | Real World
(.tf files)    | (terraform.tfstate)   | (AWS)
               |                       |
resource <span class="hljs-string">"     |   {                   | ┌─────────┐
aws_instance"</span>  |   <span class="hljs-string">"resources"</span>: [      │ | EC2     │
<span class="hljs-string">"web"</span> {        |   {                   │ | Instance│
  ...          |   <span class="hljs-attr">"type"</span>:             │ | i<span class="hljs-number">-12345</span> │
}              |   <span class="hljs-string">"aws_instance"</span>,     | └─────────┘
               |   <span class="hljs-string">"name"</span>: <span class="hljs-string">"web"</span>,      |
               |  <span class="hljs-string">"instances"</span>: [       |
               |     {                 |
               |       <span class="hljs-attr">"id"</span>: <span class="hljs-string">"i-12345"</span> |
               |     }                 |
               |   ]                   |
               | }]                    |
               |}                      |

      ▲                  ▲                      ▲
      │                  │                      │
      └──────────────────┴──────────────────────┘
              Terraform uses state to map
              code to real infrastructure
</code></pre>
<h3 id="heading-what-state-stores"><strong>What State Stores</strong></h3>
<ol>
<li><p><strong>Resource IDs</strong> - Links code to actual resources</p>
</li>
<li><p><strong>Attributes</strong> - Current resource configuration</p>
</li>
<li><p><strong>Metadata</strong> - Dependencies, provider info</p>
</li>
<li><p><strong>Outputs</strong> - Output values from your configuration</p>
</li>
</ol>
<h2 id="heading-state-file-structure">🔍 State File Structure</h2>
<p>Let’s examine a typical state file:</p>
<pre><code class="lang-json">{  <span class="hljs-attr">"version"</span>: <span class="hljs-number">4</span>,  <span class="hljs-attr">"terraform_version"</span>: <span class="hljs-string">"1.6.0"</span>,  <span class="hljs-attr">"serial"</span>: <span class="hljs-number">1</span>,  <span class="hljs-attr">"lineage"</span>: <span class="hljs-string">"a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"</span>,  <span class="hljs-attr">"outputs"</span>: {    <span class="hljs-attr">"instance_id"</span>: {      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"i-1234567890abcdef0"</span>,      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"string"</span>    }  },  <span class="hljs-attr">"resources"</span>: [    {      <span class="hljs-attr">"mode"</span>: <span class="hljs-string">"managed"</span>,      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"aws_instance"</span>,      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"web"</span>,      <span class="hljs-attr">"provider"</span>: <span class="hljs-string">"provider[\\"</span>registry.terraform.io/hashicorp/aws\\<span class="hljs-string">"]"</span>,      <span class="hljs-attr">"instances"</span>: [        {          <span class="hljs-attr">"schema_version"</span>: <span class="hljs-number">1</span>,          <span class="hljs-attr">"attributes"</span>: {            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"i-1234567890abcdef0"</span>,            <span class="hljs-attr">"ami"</span>: <span class="hljs-string">"ami-0c55b159cbfafe1f0"</span>,            <span class="hljs-attr">"instance_type"</span>: <span class="hljs-string">"t2.micro"</span>,            <span class="hljs-attr">"public_ip"</span>: <span class="hljs-string">"54.123.45.67"</span>,            ...          }        }      ]    }  ]}
</code></pre>
<h3 id="heading-key-fields"><strong>Key Fields</strong></h3>
<ul>
<li><p><strong>version</strong>: State file format version</p>
</li>
<li><p><strong>terraform_version</strong>: Terraform version used</p>
</li>
<li><p><strong>serial</strong>: Increments with each state change</p>
</li>
<li><p><strong>lineage</strong>: Unique ID for this state file</p>
</li>
<li><p><strong>resources</strong>: All managed resources</p>
</li>
<li><p><strong>outputs</strong>: Output values</p>
</li>
</ul>
<h2 id="heading-state-file-warnings">🚨 State File Warnings</h2>
<p>⚠️ <strong>IMPORTANT: State files contain sensitive data!</strong></p>
<pre><code class="lang-json">{  <span class="hljs-attr">"resources"</span>: [    {      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"aws_db_instance"</span>,      <span class="hljs-attr">"attributes"</span>: {        <span class="hljs-attr">"password"</span>: <span class="hljs-string">"supersecret123"</span>,  <span class="hljs-comment">// 🔴 Sensitive!        "endpoint": "db.example.com",        ...      }    }  ]}</span>
</code></pre>
<p><strong>State files may contain:</strong></p>
<ul>
<li><p>Passwords</p>
</li>
<li><p>API keys</p>
</li>
<li><p>Private IPs</p>
</li>
<li><p>SSH keys</p>
</li>
<li><p>Any sensitive resource attributes</p>
</li>
</ul>
<p><strong>Security rules:</strong></p>
<ul>
<li><p>❌ Never commit state to version control</p>
</li>
<li><p>❌ Never share state files publicly</p>
</li>
<li><p>✅ Use remote state with encryption</p>
</li>
<li><p>✅ Enable state locking</p>
</li>
<li><p>✅ Restrict access to state</p>
</li>
</ul>
<h2 id="heading-state-commands">🎛️ State Commands</h2>
<p>Terraform provides commands to inspect and manage state:</p>
<h3 id="heading-1-show-state"><strong>1. Show State</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Show all resources</span>
terraform show

<span class="hljs-comment"># Show in JSON format</span>
terraform show -json

<span class="hljs-comment"># Show specific resource</span>
terraform state show aws_instance.web
</code></pre>
<h3 id="heading-2-list-resources"><strong>2. List Resources</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List all resources in state</span>
terraform state list

<span class="hljs-comment"># Filter by pattern</span>
terraform state list | grep vpc
</code></pre>
<h3 id="heading-3-move-resources"><strong>3. Move Resources</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Rename resource in state</span>
terraform state mv aws_instance.web aws_instance.webserver

<span class="hljs-comment"># Move resource to a module</span>
terraform state mv aws_instance.web module.web.aws_instance.server
</code></pre>
<h3 id="heading-4-remove-resources"><strong>4. Remove Resources</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Remove from state (doesn't delete real resource)</span>
terraform state rm aws_instance.web

<span class="hljs-comment"># Remove all instances of a resource type</span>
terraform state rm <span class="hljs-string">'aws_subnet.public[*]'</span>
</code></pre>
<h3 id="heading-5-pullpush-state"><strong>5. Pull/Push State</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Download remote state to stdout</span>
terraform state pull

<span class="hljs-comment"># Manually upload state (dangerous!)</span>
terraform state push terraform.tfstate
</code></pre>
<h3 id="heading-6-replace-provider"><strong>6. Replace Provider</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Update provider in state</span>
terraform state replace-provider hashicorp/aws registry.terraform.io/hashicorp/aws
</code></pre>
<h2 id="heading-local-vs-remote-state">💾 Local vs Remote State</h2>
<h3 id="heading-local-state-default"><strong>Local State (Default)</strong></h3>
<pre><code class="lang-json">project/
├── main.tf
├── terraform.tfstate         ← Stored locally
└── terraform.tfstate.backup  ← Previous version
</code></pre>
<p><strong>Pros:</strong></p>
<ul>
<li><p>Simple setup</p>
</li>
<li><p>No additional configuration</p>
</li>
<li><p>Fast access</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><p>❌ No collaboration (can’t share)</p>
</li>
<li><p>❌ No locking (risk of concurrent changes)</p>
</li>
<li><p>❌ No encryption at rest</p>
</li>
<li><p>❌ Risk of loss (local file)</p>
</li>
</ul>
<h3 id="heading-remote-state-recommended"><strong>Remote State (Recommended)</strong></h3>
<p>State stored in shared location (S3, Terraform Cloud, etc.)</p>
<p><strong>Pros:</strong></p>
<ul>
<li><p>✅ Team collaboration</p>
</li>
<li><p>✅ State locking prevents conflicts</p>
</li>
<li><p>✅ Encryption at rest</p>
</li>
<li><p>✅ Versioning and backup</p>
</li>
<li><p>✅ Audit trail</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><p>Requires additional setup</p>
</li>
<li><p>Network dependency</p>
</li>
</ul>
<h2 id="heading-remote-state-with-s3">🪣 Remote State with S3</h2>
<p>The most common remote backend is S3 + DynamoDB:</p>
<ul>
<li><p><strong>S3</strong>: Stores state file</p>
</li>
<li><p><strong>DynamoDB</strong>: Provides state locking</p>
</li>
</ul>
<h3 id="heading-backend-configuration"><strong>Backend Configuration</strong></h3>
<pre><code class="lang-json">terraform {
  backend <span class="hljs-attr">"s3"</span> {
    bucket         = <span class="hljs-attr">"my-terraform-state-bucket"</span>
    key            = <span class="hljs-attr">"project/terraform.tfstate"</span>
    region         = <span class="hljs-attr">"us-east-1"</span>
    encrypt        = true
    dynamodb_table = <span class="hljs-attr">"terraform-state-lock"</span>
  }
}
</code></pre>
<h2 id="heading-hands-on-lab-remote-state-with-s3">🧪 Hands-On Lab: Remote State with S3</h2>
<p>Let’s set up remote state storage with S3 and DynamoDB!</p>
<h3 id="heading-step-1-create-state-backend-infrastructure"><strong>Step 1: Create State Backend Infrastructure</strong></h3>
<p>First, we need to create S3 bucket and DynamoDB table. Create a new directory:</p>
<pre><code class="lang-bash">mkdir terraform-state-backend
<span class="hljs-built_in">cd</span> terraform-state-backend
</code></pre>
<p>Create <code>backend-setup.tf</code>:</p>
<pre><code class="lang-json"># backend-setup.tf

terraform {
  required_version = <span class="hljs-attr">"&gt;= 1.0"</span>

  required_providers {
    aws = {
      source  = <span class="hljs-attr">"hashicorp/aws"</span>
      version = <span class="hljs-attr">"~&gt; 5.0"</span>
    }
  }
}

provider <span class="hljs-string">"aws"</span> {
  region = <span class="hljs-attr">"us-east-1"</span>
}

# Random suffix for unique bucket name
resource <span class="hljs-string">"random_id"</span> <span class="hljs-string">"bucket_suffix"</span> {
  byte_length = 4
}

# S3 Bucket for Terraform State
resource <span class="hljs-string">"aws_s3_bucket"</span> <span class="hljs-string">"terraform_state"</span> {
  bucket = <span class="hljs-attr">"terraform-state-${random_id.bucket_suffix.hex}"</span>

  tags = {
    Name        = <span class="hljs-attr">"Terraform State Bucket"</span>
    Environment = <span class="hljs-attr">"Learning"</span>
  }
}

# Enable versioning
resource <span class="hljs-string">"aws_s3_bucket_versioning"</span> <span class="hljs-string">"terraform_state"</span> {
  bucket = aws_s3_bucket.terraform_state.id

  versioning_configuration {
    status = <span class="hljs-attr">"Enabled"</span>
  }
}

# Enable encryption
resource <span class="hljs-string">"aws_s3_bucket_server_side_encryption_configuration"</span> <span class="hljs-string">"terraform_state"</span> {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = <span class="hljs-attr">"AES256"</span>
    }
  }
}

# Block public access
resource <span class="hljs-string">"aws_s3_bucket_public_access_block"</span> <span class="hljs-string">"terraform_state"</span> {
  bucket = aws_s3_bucket.terraform_state.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# DynamoDB Table for State Locking
resource <span class="hljs-string">"aws_dynamodb_table"</span> <span class="hljs-string">"terraform_locks"</span> {
  name         = <span class="hljs-attr">"terraform-state-lock"</span>
  billing_mode = <span class="hljs-attr">"PAY_PER_REQUEST"</span>
  hash_key     = <span class="hljs-attr">"LockID"</span>

  attribute {
    name = <span class="hljs-attr">"LockID"</span>
    type = <span class="hljs-attr">"S"</span>
  }

  tags = {
    Name        = <span class="hljs-attr">"Terraform State Lock Table"</span>
    Environment = <span class="hljs-attr">"Learning"</span>
  }
}

# Outputs
output <span class="hljs-string">"s3_bucket_name"</span> {
  description = <span class="hljs-attr">"S3 bucket name for state"</span>
  value       = aws_s3_bucket.terraform_state.id
}

output <span class="hljs-string">"dynamodb_table_name"</span> {
  description = <span class="hljs-attr">"DynamoDB table name for locking"</span>
  value       = aws_dynamodb_table.terraform_locks.name
}

output <span class="hljs-string">"backend_config"</span> {
  description = <span class="hljs-attr">"Backend configuration to use"</span>
  value = &lt;&lt;-EOT
    terraform {
      backend <span class="hljs-attr">"s3"</span> {
        bucket         = <span class="hljs-attr">"${aws_s3_bucket.terraform_state.id}"</span>
        key            = <span class="hljs-attr">"project/terraform.tfstate"</span>
        region         = <span class="hljs-attr">"us-east-1"</span>
        encrypt        = true
        dynamodb_table = <span class="hljs-attr">"${aws_dynamodb_table.terraform_locks.name}"</span>
      }
    }
  EOT
}
</code></pre>
<p>Add <code>terraform.tf</code> for the random provider:</p>
<pre><code class="lang-json"># terraform.tf
terraform {
  required_providers {
    random = {
      source  = <span class="hljs-attr">"hashicorp/random"</span>
      version = <span class="hljs-attr">"~&gt; 3.0"</span>
    }
  }
}
</code></pre>
<h3 id="heading-step-2-create-backend-infrastructure"><strong>Step 2: Create Backend Infrastructure</strong></h3>
<pre><code class="lang-bash">terraform init
terraform apply
</code></pre>
<p>Type <code>yes</code> to create the S3 bucket and DynamoDB table.</p>
<p><strong>Save the outputs!</strong> You’ll need them.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Save bucket name</span>
terraform output -raw s3_bucket_name &gt; ../bucket_name.txt

<span class="hljs-comment"># View the backend config</span>
terraform output backend_config
</code></pre>
<h3 id="heading-step-3-create-a-project-with-remote-state"><strong>Step 3: Create a Project with Remote State</strong></h3>
<p>Create a new directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ..
mkdir terraform-remote-state-demo
<span class="hljs-built_in">cd</span> terraform-remote-state-demo
</code></pre>
<h3 id="heading-step-4-configure-remote-backend"><strong>Step 4: Configure Remote Backend</strong></h3>
<p>Create <code>backend.tf</code>:</p>
<pre><code class="lang-json"># backend.tf

terraform {
  backend <span class="hljs-attr">"s3"</span> {
    bucket         = <span class="hljs-attr">"terraform-state-XXXX"</span>  # Replace with your bucket name
    key            = <span class="hljs-attr">"demo/terraform.tfstate"</span>
    region         = <span class="hljs-attr">"us-east-1"</span>
    encrypt        = true
    dynamodb_table = <span class="hljs-attr">"terraform-state-lock"</span>
  }
}
</code></pre>
<p><strong>Replace</strong> <code>terraform-state-XXXX</code> with your actual bucket name from step 2!</p>
<h3 id="heading-step-5-create-resources"><strong>Step 5: Create Resources</strong></h3>
<p>Create <code>main.tf</code>:</p>
<pre><code class="lang-json"># main.tf

terraform {
  required_providers {
    aws = {
      source  = <span class="hljs-attr">"hashicorp/aws"</span>
      version = <span class="hljs-attr">"~&gt; 5.0"</span>
    }
  }
}

provider <span class="hljs-string">"aws"</span> {
  region = <span class="hljs-attr">"us-east-1"</span>
}

# VPC
resource <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"main"</span> {
  cidr_block = <span class="hljs-attr">"10.0.0.0/16"</span>

  tags = {
    Name = <span class="hljs-attr">"remote-state-demo-vpc"</span>
  }
}

# Subnet
resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"public"</span> {
  vpc_id     = aws_vpc.main.id
  cidr_block = <span class="hljs-attr">"10.0.1.0/24"</span>

  tags = {
    Name = <span class="hljs-attr">"remote-state-demo-subnet"</span>
  }
}

# Security Group
resource <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"web"</span> {
  name        = <span class="hljs-attr">"remote-state-demo-sg"</span>
  description = <span class="hljs-attr">"Demo security group"</span>
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = <span class="hljs-attr">"tcp"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = <span class="hljs-attr">"-1"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  tags = {
    Name = <span class="hljs-attr">"remote-state-demo-sg"</span>
  }
}
</code></pre>
<p>Create <code>outputs.tf</code>:</p>
<pre><code class="lang-json"># outputs.tf

output <span class="hljs-string">"vpc_id"</span> {
  description = <span class="hljs-attr">"VPC ID"</span>
  value       = aws_vpc.main.id
}

output <span class="hljs-string">"subnet_id"</span> {
  description = <span class="hljs-attr">"Subnet ID"</span>
  value       = aws_subnet.public.id
}

output <span class="hljs-string">"security_group_id"</span> {
  description = <span class="hljs-attr">"Security Group ID"</span>
  value       = aws_security_group.web.id
}
</code></pre>
<h3 id="heading-step-6-initialize-with-remote-backend"><strong>Step 6: Initialize with Remote Backend</strong></h3>
<pre><code class="lang-bash">terraform init
</code></pre>
<p>You’ll see:</p>
<pre><code class="lang-json">Initializing the backend...

Successfully configured the backend <span class="hljs-string">"s3"</span>!
</code></pre>
<h3 id="heading-step-7-apply-configuration"><strong>Step 7: Apply Configuration</strong></h3>
<pre><code class="lang-bash">terraform apply
</code></pre>
<p>Type <code>yes</code> to create resources.</p>
<h3 id="heading-step-8-verify-state-in-s3"><strong>Step 8: Verify State in S3</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List state files in S3</span>
aws s3 ls s3://terraform-state-XXXX/demo/

<span class="hljs-comment"># Download and view state</span>
aws s3 cp s3://terraform-state-XXXX/demo/terraform.tfstate - | jq <span class="hljs-string">'.'</span>
</code></pre>
<h3 id="heading-step-9-test-state-locking"><strong>Step 9: Test State Locking</strong></h3>
<p><strong>Terminal 1:</strong></p>
<pre><code class="lang-bash">terraform plan
<span class="hljs-comment"># Keep this running</span>
</code></pre>
<p><strong>Terminal 2 (while Terminal 1 is still running):</strong></p>
<pre><code class="lang-bash">terraform plan
</code></pre>
<p>You should see:</p>
<pre><code class="lang-json">Error: Error acquiring the state lock

Lock Info:
  ID:        abc123...
  Path:      terraform-state-XXXX/demo/terraform.tfstate
  Operation: OperationTypeApply
  Who:       user@hostname
  Version:   <span class="hljs-number">1.6</span><span class="hljs-number">.0</span>
  Created:   <span class="hljs-number">2024</span><span class="hljs-number">-01</span><span class="hljs-number">-15</span> <span class="hljs-number">10</span>:<span class="hljs-number">30</span>:<span class="hljs-number">00</span>
</code></pre>
<p>This proves state locking is working! ✅</p>
<h3 id="heading-step-10-verify-state-in-dynamodb"><strong>Step 10: Verify State in DynamoDB</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Check lock table</span>
aws dynamodb scan --table-name terraform-state-lock

<span class="hljs-comment"># During an active plan/apply, you'll see:{  "Items": [</span>
    {      <span class="hljs-string">"LockID"</span>: {
        <span class="hljs-string">"S"</span>: <span class="hljs-string">"terraform-state-XXXX/demo/terraform.tfstate"</span>      },      <span class="hljs-string">"Info"</span>: {
        <span class="hljs-string">"S"</span>: <span class="hljs-string">"{\\"</span>ID\\":\\"...\\",\\"Operation\\":\\"OperationTypePlan\\",\\"Who\\":\\"user@hostname\\",...}<span class="hljs-string">"      }    }  ]}</span>
</code></pre>
<h3 id="heading-step-11-inspect-state-commands"><strong>Step 11: Inspect State Commands</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># List resources (reads from S3)</span>
terraform state list

<span class="hljs-comment"># Show specific resource</span>
terraform state show aws_vpc.main

<span class="hljs-comment"># Pull state locally for inspection</span>
terraform state pull &gt; local_state.json
cat local_state.json | jq <span class="hljs-string">'.resources'</span>
</code></pre>
<h3 id="heading-step-12-clean-up"><strong>Step 12: Clean Up</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Destroy project resources</span>
terraform destroy

<span class="hljs-comment"># Go back and destroy backend infrastructure</span>
<span class="hljs-built_in">cd</span> ../terraform-state-backend
terraform destroy
</code></pre>
<h2 id="heading-state-security-best-practices">🔐 State Security Best Practices</h2>
<h3 id="heading-1-use-remote-state"><strong>1. Use Remote State</strong></h3>
<pre><code class="lang-json">terraform {
  backend <span class="hljs-attr">"s3"</span> {
    bucket  = <span class="hljs-attr">"terraform-state"</span>
    encrypt = true  # ✅ Always encrypt!
  }
}
</code></pre>
<h3 id="heading-2-enable-versioning"><strong>2. Enable Versioning</strong></h3>
<pre><code class="lang-json">resource <span class="hljs-string">"aws_s3_bucket_versioning"</span> <span class="hljs-string">"state"</span> {
  versioning_configuration {
    status = <span class="hljs-attr">"Enabled"</span>  # ✅ Recover from accidents
  }
}
</code></pre>
<h3 id="heading-3-restrict-access"><strong>3. Restrict Access</strong></h3>
<pre><code class="lang-json"># Use IAM policies to restrict access
{
  <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
  <span class="hljs-attr">"Action"</span>: [<span class="hljs-string">"s3:GetObject"</span>, <span class="hljs-string">"s3:PutObject"</span>],
  <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::terraform-state/*"</span>,
  <span class="hljs-attr">"Condition"</span>: {
    <span class="hljs-attr">"StringEquals"</span>: {
      <span class="hljs-attr">"aws:userid"</span>: [<span class="hljs-string">"allowed-user-id"</span>]
    }
  }
}
</code></pre>
<h3 id="heading-4-use-separate-states"><strong>4. Use Separate States</strong></h3>
<ul>
<li><p>Different states for different environments</p>
</li>
<li><p>Blast radius limitation</p>
</li>
</ul>
<pre><code class="lang-json">s3:<span class="hljs-comment">//terraform-state/</span>
├── dev/terraform.tfstate
├── staging/terraform.tfstate
└── prod/terraform.tfstate
</code></pre>
<h2 id="heading-state-best-practices">📊 State Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Use remote state</strong> for team projects</p>
</li>
<li><p><strong>Enable state locking</strong> to prevent conflicts</p>
</li>
<li><p><strong>Encrypt state at rest</strong> (S3 encryption)</p>
</li>
<li><p><strong>Enable versioning</strong> for recovery</p>
</li>
<li><p><strong>Use separate states</strong> per environment</p>
</li>
<li><p><strong>Back up state files</strong> regularly</p>
</li>
<li><p><strong>Use</strong> <code>terraform state</code> commands carefully</p>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p><strong>Don’t commit state to git</strong></p>
<pre><code class="lang-json"> # .gitignore
 *.tfstate
 *.tfstate.backup
</code></pre>
</li>
<li><p><strong>Don’t manually edit state files</strong></p>
<ul>
<li>Use <code>terraform state</code> commands instead</li>
</ul>
</li>
<li><p><strong>Don’t share state files</strong> publicly</p>
</li>
<li><p><strong>Don’t use same state</strong> for all environments</p>
</li>
<li><p><strong>Don’t forget to lock the state.</strong></p>
</li>
</ol>
<h2 id="heading-state-troubleshooting">🆘 State Troubleshooting</h2>
<h3 id="heading-problem-state-drift"><strong>Problem: State Drift</strong></h3>
<p>Resources changed outside Terraform.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Detect drift</span>
terraform plan

<span class="hljs-comment"># Refresh state</span>
terraform apply -refresh-only
</code></pre>
<h3 id="heading-problem-lost-state"><strong>Problem: Lost State</strong></h3>
<p>State file deleted or corrupted.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># With S3 versioning</span>
aws s3api list-object-versions --bucket terraform-state
<span class="hljs-comment"># Restore previous version</span>
aws s3api get-object --bucket terraform-state --key terraform.tfstate --version-id VERSION_ID terraform.tfstate
</code></pre>
<h3 id="heading-problem-state-lock-stuck"><strong>Problem: State Lock Stuck</strong></h3>
<p>Lock not released after the crash.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Force unlock (use carefully!)</span>
terraform force-unlock LOCK_ID
</code></pre>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ What Terraform state is and why it exists</p>
</li>
<li><p>✅ State file structure and contents</p>
</li>
<li><p>✅ State security considerations</p>
</li>
<li><p>✅ Essential state commands</p>
</li>
<li><p>✅ Local vs remote state</p>
</li>
<li><p>✅ S3 + DynamoDB backend setup</p>
</li>
<li><p>✅ State locking mechanism</p>
</li>
<li><p>✅ State best practices and troubleshooting</p>
</li>
</ul>
<h2 id="heading-week-1-complete">🎉 Week 1 Complete!</h2>
<p>Congratulations! You’ve completed Week 1 and learned:</p>
<ul>
<li><p>Infrastructure as Code fundamentals</p>
</li>
<li><p>Terraform installation and setup</p>
</li>
<li><p>Providers and their configuration</p>
</li>
<li><p>Variables and outputs</p>
</li>
<li><p>Resource dependencies and data sources</p>
</li>
<li><p>State management</p>
</li>
</ul>
<h2 id="heading-week-2-preview">🚀 Week 2 Preview</h2>
<p><strong>Day 8: Terraform CLI Commands Deep Dive</strong></p>
<p>Next week we’ll:</p>
<ul>
<li><p>Master all Terraform CLI commands</p>
</li>
<li><p>Learn workspace management</p>
</li>
<li><p>Explore advanced variable types</p>
</li>
<li><p>Work with complex data structures</p>
</li>
<li><p>Build more sophisticated infrastructure</p>
</li>
</ul>
<h2 id="heading-weekend-challenge">💭 Weekend Challenge</h2>
<ol>
<li><p>Create a remote state backend in your AWS account</p>
</li>
<li><p>Build a multi-tier application with remote state</p>
</li>
<li><p>Practice state commands</p>
</li>
<li><p>Set up separate states for dev and prod</p>
</li>
</ol>
<hr />
<p><a target="_blank" href="https://stackopsdiary.site/day-5-resource-dependencies-and-data-sources">← Day 5: Dependencies &amp; Data Sources</a> | <a target="_blank" href="https://stackopsdiary.site/day-8-terraform-cli-commands-deep-dive">Day 8: Terraform CLI Deep Dive →</a></p>
<hr />
<p><em>Remember: Proper state management is the foundation of reliable infrastructure automation!</em></p>
<p><strong>🎊 Enjoy your Sunday rest! See you on Monday for Week 2!</strong></p>
]]></content:encoded></item><item><title><![CDATA[Day 5: Resource Dependencies & Data Sources]]></title><description><![CDATA[Welcome to Day 5! Today we’ll explore how Terraform manages relationships between resources and how to query existing infrastructure using data sources. Understanding dependencies is crucial for building complex, reliable infrastructure.
🎯 Today’s G...]]></description><link>https://stackopsdiary.site/day-5-resource-dependencies-and-data-sources</link><guid isPermaLink="true">https://stackopsdiary.site/day-5-resource-dependencies-and-data-sources</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Fri, 10 Oct 2025 12:12:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760098290353/93d7a9f5-5a74-4978-b5a8-7dbb617235d9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 5! Today we’ll explore how Terraform manages relationships between resources and how to query existing infrastructure using <strong>data sources</strong>. Understanding dependencies is crucial for building complex, reliable infrastructure.</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Understand implicit vs explicit dependencies</p>
</li>
<li><p>Master the <code>depends_on</code> meta-argument</p>
</li>
<li><p>Learn about data sources and their uses</p>
</li>
<li><p>Query existing AWS resources</p>
</li>
<li><p>Build infrastructure that references external resources</p>
</li>
<li><p>Understand the resource graph</p>
</li>
</ul>
<h2 id="heading-resource-dependencies">🔗 Resource Dependencies</h2>
<p>When building infrastructure, resources often depend on each other. Terraform needs to know the order to create or destroy them.</p>
<h3 id="heading-example-dependency-chain"><strong>Example Dependency Chain</strong></h3>
<pre><code class="lang-json">    VPC
     │
     ├─► Subnet
     │     │
     │     └─► EC2 Instance
     │
     └─► Internet Gateway
           │
           └─► Route Table
</code></pre>
<h2 id="heading-implicit-dependencies">🤝 Implicit Dependencies</h2>
<p><strong>Implicit dependencies</strong> are automatically detected when one resource references another’s attributes.</p>
<pre><code class="lang-json"># VPC is created first (no dependencies)
resource <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"main"</span> {
  cidr_block = <span class="hljs-attr">"10.0.0.0/16"</span>
}

# Subnet depends on VPC (implicit dependency)
resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"public"</span> {
  vpc_id     = aws_vpc.main.id  # ← This creates implicit dependency
  cidr_block = <span class="hljs-attr">"10.0.1.0/24"</span>
}

# Instance depends on Subnet (implicit dependency)
resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"web"</span> {
  ami           = <span class="hljs-attr">"ami-0c55b159cbfafe1f0"</span>
  instance_type = <span class="hljs-attr">"t2.micro"</span>
  subnet_id     = aws_subnet.public.id  # ← Implicit dependency
}
</code></pre>
<p><strong>Terraform’s creation order:</strong></p>
<ol>
<li><p>VPC</p>
</li>
<li><p>Subnet (waits for VPC)</p>
</li>
<li><p>Instance (waits for Subnet)</p>
</li>
</ol>
<p><strong>Destruction order (reverse):</strong></p>
<ol>
<li><p>Instance</p>
</li>
<li><p>Subnet</p>
</li>
<li><p>VPC</p>
</li>
</ol>
<h2 id="heading-explicit-dependencies-dependson">📌 Explicit Dependencies (depends_on)</h2>
<p>Sometimes dependencies exist that Terraform can’t detect automatically. Use <code>depends_on</code> for explicit dependencies.</p>
<h3 id="heading-when-to-use-dependson"><strong>When to Use depends_on</strong></h3>
<pre><code class="lang-json"># IAM role must exist before instance profile
resource <span class="hljs-string">"aws_iam_role"</span> <span class="hljs-string">"instance_role"</span> {
  name = <span class="hljs-attr">"instance-role"</span>

  assume_role_policy = jsonencode({
    Version = <span class="hljs-attr">"2012-10-17"</span>
    Statement = [{
      Action = <span class="hljs-attr">"sts:AssumeRole"</span>
      Effect = <span class="hljs-attr">"Allow"</span>
      Principal = {
        Service = <span class="hljs-attr">"ec2.amazonaws.com"</span>
      }
    }]
  })
}

# IAM policy attachment
resource <span class="hljs-string">"aws_iam_role_policy_attachment"</span> <span class="hljs-string">"instance_policy"</span> {
  role       = aws_iam_role.instance_role.name
  policy_arn = <span class="hljs-attr">"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"</span>
}

# Instance profile needs the policy to be attached
resource <span class="hljs-string">"aws_iam_instance_profile"</span> <span class="hljs-string">"instance_profile"</span> {
  name = <span class="hljs-attr">"instance-profile"</span>
  role = aws_iam_role.instance_role.name

  # Explicit dependency - ensure policy is attached first
  depends_on = [aws_iam_role_policy_attachment.instance_policy]
}
</code></pre>
<h3 id="heading-multiple-dependencies"><strong>Multiple Dependencies</strong></h3>
<pre><code class="lang-json">resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"web"</span> {
  ami           = <span class="hljs-attr">"ami-0c55b159cbfafe1f0"</span>
  instance_type = <span class="hljs-attr">"t2.micro"</span>

  depends_on = [
    aws_iam_instance_profile.instance_profile,
    aws_security_group.web,
    aws_subnet.public
  ]
}
</code></pre>
<h2 id="heading-data-sources">🔍 Data Sources</h2>
<p><strong>Data sources</strong> allow Terraform to query existing infrastructure or external information. They don’t create resources - they only read data.</p>
<h3 id="heading-data-source-syntax"><strong>Data Source Syntax</strong></h3>
<pre><code class="lang-json">data <span class="hljs-string">"provider_resource"</span> <span class="hljs-string">"name"</span> {
  # Filter criteria
}

# Reference with: data.provider_resource.name.attribute
</code></pre>
<h3 id="heading-example-query-existing-vpc"><strong>Example: Query Existing VPC</strong></h3>
<pre><code class="lang-json"># Query existing VPC by tag
data <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"existing"</span> {
  tags = {
    Name = <span class="hljs-attr">"production-vpc"</span>
  }
}

# Use the VPC ID in a new subnet
resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"new_subnet"</span> {
  vpc_id     = data.aws_vpc.existing.id
  cidr_block = <span class="hljs-attr">"10.0.10.0/24"</span>
}
</code></pre>
<h2 id="heading-common-aws-data-sources">📚 Common AWS Data Sources</h2>
<h3 id="heading-1-aws-ami-amazon-machine-image"><strong>1. AWS AMI (Amazon Machine Image)</strong></h3>
<pre><code class="lang-json"># Get latest Amazon Linux <span class="hljs-number">2</span> AMI
data <span class="hljs-string">"aws_ami"</span> <span class="hljs-string">"amazon_linux"</span> {
  most_recent = true
  owners      = [<span class="hljs-attr">"amazon"</span>]

  filter {
    name   = <span class="hljs-attr">"name"</span>
    values = [<span class="hljs-attr">"amzn2-ami-hvm-*-x86_64-gp2"</span>]
  }

  filter {
    name   = <span class="hljs-attr">"virtualization-type"</span>
    values = [<span class="hljs-attr">"hvm"</span>]
  }
}

resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"web"</span> {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = <span class="hljs-attr">"t2.micro"</span>
}
</code></pre>
<h3 id="heading-2-aws-availability-zones"><strong>2. AWS Availability Zones</strong></h3>
<pre><code class="lang-json"># Get all available AZs in current region
data <span class="hljs-string">"aws_availability_zones"</span> <span class="hljs-string">"available"</span> {
  state = <span class="hljs-attr">"available"</span>
}

resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"public"</span> {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = <span class="hljs-attr">"10.0.${count.index}.0/24"</span>
  availability_zone = data.aws_availability_zones.available.names[count.index]
}
</code></pre>
<h3 id="heading-3-aws-account-information"><strong>3. AWS Account Information</strong></h3>
<pre><code class="lang-json">data <span class="hljs-string">"aws_caller_identity"</span> <span class="hljs-string">"current"</span> {}

output <span class="hljs-string">"account_id"</span> {
  value = data.aws_caller_identity.current.account_id
}

output <span class="hljs-string">"caller_arn"</span> {
  value = data.aws_caller_identity.current.arn
}
</code></pre>
<h3 id="heading-4-aws-region"><strong>4. AWS Region</strong></h3>
<pre><code class="lang-json">data <span class="hljs-string">"aws_region"</span> <span class="hljs-string">"current"</span> {}

output <span class="hljs-string">"current_region"</span> {
  value = data.aws_region.current.name
}
</code></pre>
<h3 id="heading-5-existing-security-group"><strong>5. Existing Security Group</strong></h3>
<pre><code class="lang-json">data <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"default"</span> {
  name   = <span class="hljs-attr">"default"</span>
  vpc_id = aws_vpc.main.id
}
</code></pre>
<h3 id="heading-6-existing-subnet"><strong>6. Existing Subnet</strong></h3>
<pre><code class="lang-json">data <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"selected"</span> {
  filter {
    name   = <span class="hljs-attr">"tag:Name"</span>
    values = [<span class="hljs-attr">"production-subnet-1"</span>]
  }
}
</code></pre>
<h2 id="heading-hands-on-lab-dependencies-amp-data-sources">🧪 Hands-On Lab: Dependencies &amp; Data Sources</h2>
<p>Let’s build a complete infrastructure using both implicit/explicit dependencies and data sources!</p>
<h3 id="heading-step-1-create-project-directory"><strong>Step 1: Create Project Directory</strong></h3>
<pre><code class="lang-bash">mkdir terraform-dependencies-lab
<span class="hljs-built_in">cd</span> terraform-dependencies-lab
</code></pre>
<h3 id="heading-step-2-create-data-sourcestfhttpdata-sourcestf"><strong>Step 2: Create</strong> <a target="_blank" href="http://data-sources.tf"><strong>data-sources.tf</strong></a></h3>
<pre><code class="lang-json"># data-sources.tf

# Get current AWS region
data <span class="hljs-string">"aws_region"</span> <span class="hljs-string">"current"</span> {}

# Get current AWS account
data <span class="hljs-string">"aws_caller_identity"</span> <span class="hljs-string">"current"</span> {}

# Get available availability zones
data <span class="hljs-string">"aws_availability_zones"</span> <span class="hljs-string">"available"</span> {
  state = <span class="hljs-attr">"available"</span>
}

# Get latest Amazon Linux <span class="hljs-number">2</span> AMI
data <span class="hljs-string">"aws_ami"</span> <span class="hljs-string">"amazon_linux"</span> {
  most_recent = true
  owners      = [<span class="hljs-attr">"amazon"</span>]

  filter {
    name   = <span class="hljs-attr">"name"</span>
    values = [<span class="hljs-attr">"amzn2-ami-hvm-*-x86_64-gp2"</span>]
  }

  filter {
    name   = <span class="hljs-attr">"virtualization-type"</span>
    values = [<span class="hljs-attr">"hvm"</span>]
  }

  filter {
    name   = <span class="hljs-attr">"root-device-type"</span>
    values = [<span class="hljs-attr">"ebs"</span>]
  }
}
</code></pre>
<h3 id="heading-step-3-create-maintfhttpmaintf"><strong>Step 3: Create</strong> <a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-json"># main.tf

terraform {
  required_version = <span class="hljs-attr">"&gt;= 1.0"</span>

  required_providers {
    aws = {
      source  = <span class="hljs-attr">"hashicorp/aws"</span>
      version = <span class="hljs-attr">"~&gt; 5.0"</span>
    }
  }
}

provider <span class="hljs-string">"aws"</span> {
  region = <span class="hljs-attr">"us-east-1"</span>
}

# VPC
resource <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"main"</span> {
  cidr_block           = <span class="hljs-attr">"10.0.0.0/16"</span>
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = <span class="hljs-attr">"dependencies-lab-vpc"</span>
  }
}

# Internet Gateway (implicit dependency on VPC)
resource <span class="hljs-string">"aws_internet_gateway"</span> <span class="hljs-string">"main"</span> {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = <span class="hljs-attr">"dependencies-lab-igw"</span>
  }
}

# Public Subnets using data source for AZs
resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"public"</span> {
  count = 2

  vpc_id                  = aws_vpc.main.id
  cidr_block              = <span class="hljs-attr">"10.0.${count.index + 1}.0/24"</span>
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = <span class="hljs-attr">"public-subnet-${count.index + 1}"</span>
    AZ   = data.aws_availability_zones.available.names[count.index]
  }
}

# Route Table
resource <span class="hljs-string">"aws_route_table"</span> <span class="hljs-string">"public"</span> {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = <span class="hljs-attr">"0.0.0.0/0"</span>
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = <span class="hljs-attr">"public-route-table"</span>
  }
}

# Route Table Association
resource <span class="hljs-string">"aws_route_table_association"</span> <span class="hljs-string">"public"</span> {
  count = 2

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"web"</span> {
  name        = <span class="hljs-attr">"web-security-group"</span>
  description = <span class="hljs-attr">"Allow HTTP and SSH"</span>
  vpc_id      = aws_vpc.main.id

  ingress {
    description = <span class="hljs-attr">"SSH"</span>
    from_port   = 22
    to_port     = 22
    protocol    = <span class="hljs-attr">"tcp"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  ingress {
    description = <span class="hljs-attr">"HTTP"</span>
    from_port   = 80
    to_port     = 80
    protocol    = <span class="hljs-attr">"tcp"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = <span class="hljs-attr">"-1"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  tags = {
    Name = <span class="hljs-attr">"web-sg"</span>
  }
}

# IAM Role for EC2
resource <span class="hljs-string">"aws_iam_role"</span> <span class="hljs-string">"ec2_role"</span> {
  name = <span class="hljs-attr">"ec2-ssm-role"</span>

  assume_role_policy = jsonencode({
    Version = <span class="hljs-attr">"2012-10-17"</span>
    Statement = [{
      Action = <span class="hljs-attr">"sts:AssumeRole"</span>
      Effect = <span class="hljs-attr">"Allow"</span>
      Principal = {
        Service = <span class="hljs-attr">"ec2.amazonaws.com"</span>
      }
    }]
  })

  tags = {
    Name = <span class="hljs-attr">"ec2-ssm-role"</span>
  }
}

# Attach SSM policy to role
resource <span class="hljs-string">"aws_iam_role_policy_attachment"</span> <span class="hljs-string">"ssm_policy"</span> {
  role       = aws_iam_role.ec2_role.name
  policy_arn = <span class="hljs-attr">"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"</span>
}

# Instance Profile (explicit dependency on policy attachment)
resource <span class="hljs-string">"aws_iam_instance_profile"</span> <span class="hljs-string">"ec2_profile"</span> {
  name = <span class="hljs-attr">"ec2-instance-profile"</span>
  role = aws_iam_role.ec2_role.name

  # Explicit dependency to ensure policy is attached first
  depends_on = [aws_iam_role_policy_attachment.ssm_policy]
}

# EC2 Instance using AMI from data source
resource <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"web"</span> {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = <span class="hljs-attr">"t2.micro"</span>
  subnet_id              = aws_subnet.public[0].id
  vpc_security_group_ids = [aws_security_group.web.id]
  iam_instance_profile   = aws_iam_instance_profile.ec2_profile.name

  user_data = &lt;&lt;-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo <span class="hljs-attr">"&lt;h1&gt;Hello from Terraform!&lt;/h1&gt;"</span> &gt; /var/www/html/index.html
              echo <span class="hljs-attr">"&lt;p&gt;Instance in ${data.aws_availability_zones.available.names[0]}&lt;/p&gt;"</span> &gt;&gt; /var/www/html/index.html
              echo <span class="hljs-attr">"&lt;p&gt;AMI: ${data.aws_ami.amazon_linux.id}&lt;/p&gt;"</span> &gt;&gt; /var/www/html/index.html
              EOF

  tags = {
    Name = <span class="hljs-attr">"web-server"</span>
  }

  # Explicit dependency on route table association
  depends_on = [aws_route_table_association.public]
}
</code></pre>
<h3 id="heading-step-4-create-outputstfhttpoutputstf"><strong>Step 4: Create</strong> <a target="_blank" href="http://outputs.tf"><strong>outputs.tf</strong></a></h3>
<pre><code class="lang-json"># outputs.tf

output <span class="hljs-string">"account_id"</span> {
  description = <span class="hljs-attr">"AWS Account ID"</span>
  value       = data.aws_caller_identity.current.account_id
}

output <span class="hljs-string">"region"</span> {
  description = <span class="hljs-attr">"AWS Region"</span>
  value       = data.aws_region.current.name
}

output <span class="hljs-string">"availability_zones"</span> {
  description = <span class="hljs-attr">"Available AZs"</span>
  value       = data.aws_availability_zones.available.names
}

output <span class="hljs-string">"ami_id"</span> {
  description = <span class="hljs-attr">"AMI ID used for instance"</span>
  value       = data.aws_ami.amazon_linux.id
}

output <span class="hljs-string">"ami_name"</span> {
  description = <span class="hljs-attr">"AMI name"</span>
  value       = data.aws_ami.amazon_linux.name
}

output <span class="hljs-string">"vpc_id"</span> {
  description = <span class="hljs-attr">"VPC ID"</span>
  value       = aws_vpc.main.id
}

output <span class="hljs-string">"subnet_ids"</span> {
  description = <span class="hljs-attr">"Subnet IDs"</span>
  value       = aws_subnet.public[*].id
}

output <span class="hljs-string">"instance_id"</span> {
  description = <span class="hljs-attr">"EC2 Instance ID"</span>
  value       = aws_instance.web.id
}

output <span class="hljs-string">"instance_public_ip"</span> {
  description = <span class="hljs-attr">"EC2 Instance Public IP"</span>
  value       = aws_instance.web.public_ip
}

output <span class="hljs-string">"website_url"</span> {
  description = <span class="hljs-attr">"Website URL"</span>
  value       = <span class="hljs-attr">"http://${aws_instance.web.public_ip}"</span>
}
</code></pre>
<h3 id="heading-step-5-initialize-and-plan"><strong>Step 5: Initialize and Plan</strong></h3>
<pre><code class="lang-bash">terraform init
terraform plan
</code></pre>
<p><strong>Notice in the plan:</strong></p>
<ul>
<li><p>Data sources are read first</p>
</li>
<li><p>Resources are created in dependency order</p>
</li>
<li><p>Implicit dependencies shown with arrows</p>
</li>
</ul>
<h3 id="heading-step-6-visualize-dependencies"><strong>Step 6: Visualize Dependencies</strong></h3>
<pre><code class="lang-bash">terraform graph | dot -Tpng &gt; dependencies.png
</code></pre>
<p>Open <code>dependencies.png</code> to see the dependency graph!</p>
<h3 id="heading-step-7-apply-configuration"><strong>Step 7: Apply Configuration</strong></h3>
<pre><code class="lang-bash">terraform apply
</code></pre>
<p>Type <code>yes</code> to confirm.</p>
<h3 id="heading-step-8-test-the-website"><strong>Step 8: Test the Website</strong></h3>
<p>After apply completes:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Get the website URL</span>
terraform output website_url
<span class="hljs-comment"># Test with curl</span>
curl $(terraform output -raw website_url)
</code></pre>
<p>You should see the HTML page with instance details!</p>
<h3 id="heading-step-9-examine-data-source-values"><strong>Step 9: Examine Data Source Values</strong></h3>
<pre><code class="lang-bash">terraform output ami_id
terraform output ami_name
terraform output availability_zones
</code></pre>
<p>These values were queried from AWS, not hardcoded!</p>
<h3 id="heading-step-10-understand-the-dependency-chain"><strong>Step 10: Understand the Dependency Chain</strong></h3>
<pre><code class="lang-json">Data Sources (Read First)
├── aws_region.current
├── aws_caller_identity.current
├── aws_availability_zones.available
└── aws_ami.amazon_linux

Resources (Created in Order)
├── <span class="hljs-number">1.</span> aws_vpc.main
├── <span class="hljs-number">2.</span> aws_internet_gateway.main (depends on VPC)
├── <span class="hljs-number">3.</span> aws_subnet.public[<span class="hljs-number">0</span>,<span class="hljs-number">1</span>] (depends on VPC, uses AZ data)
├── <span class="hljs-number">4.</span> aws_security_group.web (depends on VPC)
├── <span class="hljs-number">5.</span> aws_iam_role.ec2_role
├── <span class="hljs-number">6.</span> aws_iam_role_policy_attachment.ssm_policy (depends on role)
├── <span class="hljs-number">7.</span> aws_iam_instance_profile.ec2_profile (explicit depends_on policy)
├── <span class="hljs-number">8.</span> aws_route_table.public (depends on VPC and IGW)
├── <span class="hljs-number">9.</span> aws_route_table_association.public[<span class="hljs-number">0</span>,<span class="hljs-number">1</span>] (depends on subnet and RT)
└── <span class="hljs-number">10.</span> aws_instance.web (depends on subnet, SG, profile, uses AMI data)
</code></pre>
<h3 id="heading-step-11-clean-up"><strong>Step 11: Clean Up</strong></h3>
<pre><code class="lang-bash">terraform destroy
</code></pre>
<p>Terraform destroys in reverse dependency order!</p>
<h2 id="heading-resource-graph">🎨 Resource Graph</h2>
<p>Terraform builds a dependency graph to determine execution order:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Generate graph in DOT format</span>
terraform graph
<span class="hljs-comment"># With specific plan</span>
terraform graph -<span class="hljs-built_in">type</span>=plan
<span class="hljs-comment"># For destroy operations</span>
terraform graph -<span class="hljs-built_in">type</span>=plan-destroy
</code></pre>
<h2 id="heading-data-sources-vs-resources">📊 Data Sources vs Resources</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Data Source</td><td>Resource</td></tr>
</thead>
<tbody>
<tr>
<td>Reads existing infrastructure</td><td>Creates new infrastructure</td></tr>
<tr>
<td><code>data "aws_vpc" "main"</code></td><td><code>resource "aws_vpc" "main"</code></td></tr>
<tr>
<td>Referenced with <code>data.aws_vpc.main</code></td><td>Referenced with <code>aws_vpc.main</code></td></tr>
<tr>
<td>Read-only</td><td>Create/Update/Delete</td></tr>
<tr>
<td>No state changes</td><td>Manages state</td></tr>
</tbody>
</table>
</div><h2 id="heading-key-concepts">🔑 Key Concepts</h2>
<h3 id="heading-implicit-dependencies-1"><strong>Implicit Dependencies</strong></h3>
<ul>
<li><p>Created automatically when referencing attributes</p>
</li>
<li><p>Most common type</p>
</li>
<li><p>Terraform detects them automatically</p>
</li>
</ul>
<h3 id="heading-explicit-dependencies"><strong>Explicit Dependencies</strong></h3>
<ul>
<li><p>Use <code>depends_on</code> meta-argument</p>
</li>
<li><p>For non-obvious dependencies</p>
</li>
<li><p>Accepts list of resources</p>
</li>
</ul>
<h3 id="heading-data-sources-1"><strong>Data Sources</strong></h3>
<ul>
<li><p>Query existing infrastructure</p>
</li>
<li><p>Read external information</p>
</li>
<li><p>Don’t create resources</p>
</li>
<li><p>Evaluated before resources</p>
</li>
</ul>
<h2 id="heading-best-practices">📝 Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Prefer implicit dependencies</strong></p>
<pre><code class="lang-json"> subnet_id = aws_subnet.main.id  # ✅ Implicit
</code></pre>
</li>
<li><p><strong>Use depends_on sparingly</strong></p>
<pre><code class="lang-json"> # Only when necessary
 depends_on = [aws_iam_role_policy_attachment.policy]
</code></pre>
</li>
<li><p><strong>Use data sources for existing resources</strong></p>
<pre><code class="lang-json"> data <span class="hljs-string">"aws_ami"</span> <span class="hljs-string">"latest"</span> {
   most_recent = true
 }
</code></pre>
</li>
<li><p><strong>Document why depends_on is needed</strong></p>
<pre><code class="lang-json"> depends_on = [aws_route_table.main]
 # Ensure route exists before instance tries to access internet
</code></pre>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p>Don’t use depends_on when implicit dependencies work</p>
</li>
<li><p>Don’t create circular dependencies</p>
</li>
<li><p>Don’t hardcode AMI IDs - use data sources</p>
</li>
<li><p>Don’t assume resource creation order without dependencies</p>
</li>
</ol>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Implicit vs explicit dependencies</p>
</li>
<li><p>✅ When and how to use <code>depends_on</code></p>
</li>
<li><p>✅ Data sources and their purpose</p>
</li>
<li><p>✅ Common AWS data sources</p>
</li>
<li><p>✅ How Terraform builds the resource graph</p>
</li>
<li><p>✅ Best practices for managing dependencies</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 6: State Management Fundamentals</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Deep dive into Terraform state</p>
</li>
<li><p>Understand state file structure</p>
</li>
<li><p>Learn about state locking</p>
</li>
<li><p>Configure remote state backends</p>
</li>
<li><p>Master state commands</p>
</li>
<li><p>Implement S3 backend for state storage</p>
</li>
</ul>
<h2 id="heading-challenge-exercise">💭 Challenge Exercise</h2>
<p>Modify today’s lab to:</p>
<ol>
<li><p>Use a data source to find an existing S3 bucket</p>
</li>
<li><p>Add a resource that depends on both the VPC and the bucket</p>
</li>
<li><p>Create an explicit dependency between two resources</p>
</li>
<li><p>Add a data source for AWS SSM parameters</p>
</li>
</ol>
<hr />
<p><a target="_blank" href="https://stackopsdiary.hashnode.dev/day-4-terraform-basics-variables-and-outputs">← Day 4: <strong>Variables &amp; Outputs</strong></a> | <a target="_blank" href="https://stackopsdiary.site/day-6-state-management-fundamentals">Day 6: State Management →</a></p>
<hr />
<p><em>Remember: Understanding dependencies is crucial for building reliable, complex infrastructure!</em></p>
]]></content:encoded></item><item><title><![CDATA[Day 4: Terraform Basics - Variables & Outputs]]></title><description><![CDATA[Welcome to Day 4! Today we’ll learn how to make our Terraform configurations flexible and reusable using variables and outputs. No more hardcoded values—we’re writing professional, maintainable code!
🎯 Today’s Goals

Understand input variables and w...]]></description><link>https://stackopsdiary.site/day-4-terraform-basics-variables-and-outputs</link><guid isPermaLink="true">https://stackopsdiary.site/day-4-terraform-basics-variables-and-outputs</guid><category><![CDATA[Terraform]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[StackOps - Diary]]></dc:creator><pubDate>Thu, 09 Oct 2025 12:30:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760005549955/a9cb36b4-4d27-4c3b-bfdb-621b8dd2e025.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Day 4! Today we’ll learn how to make our Terraform configurations flexible and reusable using <strong>variables</strong> and <strong>outputs</strong>. No more hardcoded values—we’re writing professional, maintainable code!</p>
<h2 id="heading-todays-goals">🎯 Today’s Goals</h2>
<ul>
<li><p>Understand input variables and why they matter</p>
</li>
<li><p>Learn variable types and validation</p>
</li>
<li><p>Master output values</p>
</li>
<li><p>Create reusable, parameterized configurations</p>
</li>
<li><p>Build a complete infrastructure with variables</p>
</li>
</ul>
<h2 id="heading-the-problem-with-hardcoded-values">🔄 The Problem with Hardcoded Values</h2>
<p>Remember our VPC from yesterday?</p>
<pre><code class="lang-json">resource <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"main"</span> {
  cidr_block = <span class="hljs-attr">"10.0.0.0/16"</span>  # ❌ Hardcoded

  tags = {
    Name = <span class="hljs-attr">"main-vpc"</span>  # ❌ Hardcoded
  }
}
</code></pre>
<p><strong>Problems:</strong></p>
<ul>
<li><p>Can’t reuse for different environments (dev, staging, prod)</p>
</li>
<li><p>Changing values requires editing code</p>
</li>
<li><p>Difficult to customize deployments</p>
</li>
<li><p>Not DRY (Don’t Repeat Yourself)</p>
</li>
</ul>
<h2 id="heading-input-variables">📥 Input Variables</h2>
<p><strong>Input variables</strong> are parameters for your Terraform configuration. Think of them like function arguments in programming.</p>
<h3 id="heading-basic-variable-syntax"><strong>Basic Variable Syntax</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"variable_name"</span> {
  description = <span class="hljs-attr">"Description of the variable"</span>
  type        = string
  default     = <span class="hljs-attr">"default_value"</span>
}
</code></pre>
<h3 id="heading-variable-structure-explained"><strong>Variable Structure Explained</strong></h3>
<pre><code class="lang-json">┌──────────────────────────────────────┐
│ variable <span class="hljs-string">"vpc_cidr"</span> {                │
│          ────┬────                   │
│              │                       │
│         Variable name                │
│                                      │
│   description = <span class="hljs-attr">"VPC CIDR block"</span>     │
│   ──────┬──────   ──────┬────────    │
│         │                │           │
│    Attribute         Value           │
│                                      │
│   type    = string                   │
│   default = <span class="hljs-attr">"10.0.0.0/16"</span>            │
│ }                                    │
└──────────────────────────────────────┘
</code></pre>
<h3 id="heading-using-variables"><strong>Using Variables</strong></h3>
<p>Reference variables with <code>var.</code> prefix:</p>
<pre><code class="lang-json">resource <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"main"</span> {
  cidr_block = var.vpc_cidr

  tags = {
    Name = var.vpc_name
  }
}
</code></pre>
<h2 id="heading-variable-types">📊 Variable Types</h2>
<p>Terraform supports several variable types:</p>
<h3 id="heading-1-string"><strong>1. String</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"region"</span> {
  type    = string
  default = <span class="hljs-attr">"us-east-1"</span>
}
</code></pre>
<h3 id="heading-2-number"><strong>2. Number</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"instance_count"</span> {
  type    = number
  default = 2
}
</code></pre>
<h3 id="heading-3-bool"><strong>3. Bool</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"enable_dns"</span> {
  type    = bool
  default = true
}
</code></pre>
<h3 id="heading-4-list"><strong>4. List</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"availability_zones"</span> {
  type    = list(string)
  default = [<span class="hljs-attr">"us-east-1a"</span>, <span class="hljs-attr">"us-east-1b"</span>]
}

# Access: var.availability_zones[<span class="hljs-number">0</span>]
</code></pre>
<h3 id="heading-5-map"><strong>5. Map</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"instance_types"</span> {
  type = map(string)
  default = {
    dev  = <span class="hljs-attr">"t2.micro"</span>
    prod = <span class="hljs-attr">"t3.large"</span>
  }
}

# Access: var.instance_types[<span class="hljs-string">"dev"</span>]
</code></pre>
<h3 id="heading-6-object"><strong>6. Object</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"vpc_config"</span> {
  type = object({
    cidr_block = string
    name       = string
    enable_dns = bool
  })

  default = {
    cidr_block = <span class="hljs-attr">"10.0.0.0/16"</span>
    name       = <span class="hljs-attr">"main-vpc"</span>
    enable_dns = true
  }
}

# Access: var.vpc_config.cidr_block
</code></pre>
<h2 id="heading-variable-definition-precedence">🎯 Variable Definition Precedence</h2>
<p>Terraform loads variables in this order (later sources override earlier):</p>
<pre><code class="lang-json"><span class="hljs-number">1.</span> Environment variables      (TF_VAR_name)
<span class="hljs-number">2.</span> terraform.tfvars           (auto-loaded)
<span class="hljs-number">3.</span> *.auto.tfvars              (auto-loaded alphabetically)
<span class="hljs-number">4.</span> -var flag                  (command line)
<span class="hljs-number">5.</span> -var-file flag             (command line)
</code></pre>
<p><strong>Example:</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Environment variable</span>
<span class="hljs-built_in">export</span> TF_VAR_region=<span class="hljs-string">"us-west-2"</span>

<span class="hljs-comment"># terraform.tfvars file</span>
region = <span class="hljs-string">"us-east-1"</span>

<span class="hljs-comment"># Command line (highest priority)</span>
terraform apply -var=<span class="hljs-string">"region=eu-west-1"</span>

<span class="hljs-comment"># Result: region = "eu-west-1"</span>
</code></pre>
<h2 id="heading-ways-to-set-variable-values">📝 Ways to Set Variable Values</h2>
<h3 id="heading-method-1-default-values-in-variablestfhttpvariablestf"><strong>Method 1: Default Values (in</strong> <a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a><strong>)</strong></h3>
<pre><code class="lang-json">variable <span class="hljs-string">"region"</span> {
  type    = string
  default = <span class="hljs-attr">"us-east-1"</span>
}
</code></pre>
<h3 id="heading-method-2-terraformtfvars-file"><strong>Method 2: terraform.tfvars File</strong></h3>
<pre><code class="lang-json"># terraform.tfvars
region         = <span class="hljs-string">"us-east-1"</span>
instance_type  = <span class="hljs-string">"t2.micro"</span>
instance_count = <span class="hljs-number">3</span>
</code></pre>
<h3 id="heading-method-3-custom-tfvars-file"><strong>Method 3: Custom .tfvars File</strong></h3>
<pre><code class="lang-json"># production.tfvars
region         = <span class="hljs-string">"us-east-1"</span>
instance_type  = <span class="hljs-string">"t3.large"</span>
instance_count = <span class="hljs-number">10</span>
</code></pre>
<p>Apply with:</p>
<pre><code class="lang-bash">terraform apply -var-file=<span class="hljs-string">"production.tfvars"</span>
</code></pre>
<h3 id="heading-method-4-environment-variables"><strong>Method 4: Environment Variables</strong></h3>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> TF_VAR_region=<span class="hljs-string">"us-east-1"</span>
<span class="hljs-built_in">export</span> TF_VAR_instance_type=<span class="hljs-string">"t2.micro"</span>
terraform apply
</code></pre>
<h3 id="heading-method-5-command-line"><strong>Method 5: Command Line</strong></h3>
<pre><code class="lang-bash">terraform apply -var=<span class="hljs-string">"region=us-east-1"</span> -var=<span class="hljs-string">"instance_type=t2.micro"</span>
</code></pre>
<h3 id="heading-method-6-interactive-prompt"><strong>Method 6: Interactive Prompt</strong></h3>
<p>If no value is provided and there’s no default:</p>
<pre><code class="lang-bash">$ terraform apply
var.region  Enter a value: us-east-1
</code></pre>
<h2 id="heading-variable-validation">✅ Variable Validation</h2>
<p>Add validation rules to ensure correct values:</p>
<pre><code class="lang-json">variable <span class="hljs-string">"region"</span> {
  type        = string
  description = <span class="hljs-attr">"AWS region"</span>

  validation {
    condition     = can(regex(<span class="hljs-attr">"^(us|eu|ap)-"</span>, var.region))
    error_message = <span class="hljs-attr">"Region must start with us-, eu-, or ap-."</span>
  }
}

variable <span class="hljs-string">"instance_count"</span> {
  type        = number
  description = <span class="hljs-attr">"Number of instances"</span>

  validation {
    condition     = var.instance_count &gt; 0 &amp;&amp; var.instance_count &lt;= 10
    error_message = <span class="hljs-attr">"Instance count must be between 1 and 10."</span>
  }
}
</code></pre>
<h2 id="heading-output-values">📤 Output Values</h2>
<p><strong>Outputs</strong> expose information about your infrastructure after it’s created.</p>
<h3 id="heading-basic-output-syntax"><strong>Basic Output Syntax</strong></h3>
<pre><code class="lang-json">output <span class="hljs-string">"output_name"</span> {
  description = <span class="hljs-attr">"Description of the output"</span>
  value       = resource.name.attribute
}
</code></pre>
<h3 id="heading-example-outputs"><strong>Example Outputs</strong></h3>
<pre><code class="lang-json">output <span class="hljs-string">"vpc_id"</span> {
  description = <span class="hljs-attr">"ID of the VPC"</span>
  value       = aws_vpc.main.id
}

output <span class="hljs-string">"public_subnet_ids"</span> {
  description = <span class="hljs-attr">"IDs of public subnets"</span>
  value       = aws_subnet.public[*].id
}

output <span class="hljs-string">"instance_public_ips"</span> {
  description = <span class="hljs-attr">"Public IPs of instances"</span>
  value       = aws_instance.web[*].public_ip
}
</code></pre>
<h3 id="heading-sensitive-outputs"><strong>Sensitive Outputs</strong></h3>
<p>Mark sensitive data to hide from console output:</p>
<pre><code class="lang-json">output <span class="hljs-string">"database_password"</span> {
  description = <span class="hljs-attr">"Database password"</span>
  value       = aws_db_instance.main.password
  sensitive   = true
}
</code></pre>
<h3 id="heading-using-outputs"><strong>Using Outputs</strong></h3>
<p>View outputs after applying:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># View output</span>
terraform output

<span class="hljs-comment"># View specific output</span>
terraform output vpc_id

<span class="hljs-comment"># Output as JSON</span>
terraform output -json
</code></pre>
<h2 id="heading-hands-on-lab-parameterized-vpc-infrastructure">🧪 Hands-On Lab: Parameterized VPC Infrastructure</h2>
<p>Let’s rebuild our VPC with variables and outputs!</p>
<h3 id="heading-step-1-create-project-structure"><strong>Step 1: Create Project Structure</strong></h3>
<pre><code class="lang-bash">mkdir terraform-variables-lab
<span class="hljs-built_in">cd</span> terraform-variables-lab
</code></pre>
<p>Create these files:</p>
<ul>
<li><p><code>variables.tf</code> - Variable definitions</p>
</li>
<li><p><code>main.tf</code> - Resources</p>
</li>
<li><p><code>outputs.tf</code> - Output values</p>
</li>
<li><p><code>terraform.tfvars</code> - Variable values</p>
</li>
</ul>
<h3 id="heading-step-2-create-variablestfhttpvariablestf"><strong>Step 2: Create</strong> <a target="_blank" href="http://variables.tf"><strong>variables.tf</strong></a></h3>
<pre><code class="lang-json"># variables.tf

variable <span class="hljs-string">"aws_region"</span> {
  description = <span class="hljs-attr">"AWS region for resources"</span>
  type        = string
  default     = <span class="hljs-attr">"us-east-1"</span>
}

variable <span class="hljs-string">"project_name"</span> {
  description = <span class="hljs-attr">"Project name for tagging"</span>
  type        = string
  default     = <span class="hljs-attr">"terraform-learning"</span>
}

variable <span class="hljs-string">"environment"</span> {
  description = <span class="hljs-attr">"Environment (dev, staging, prod)"</span>
  type        = string

  validation {
    condition     = contains([<span class="hljs-attr">"dev"</span>, <span class="hljs-attr">"staging"</span>, <span class="hljs-attr">"prod"</span>], var.environment)
    error_message = <span class="hljs-attr">"Environment must be dev, staging, or prod."</span>
  }
}

variable <span class="hljs-string">"vpc_cidr"</span> {
  description = <span class="hljs-attr">"CIDR block for VPC"</span>
  type        = string
  default     = <span class="hljs-attr">"10.0.0.0/16"</span>
}

variable <span class="hljs-string">"public_subnet_cidrs"</span> {
  description = <span class="hljs-attr">"CIDR blocks for public subnets"</span>
  type        = list(string)
  default     = [<span class="hljs-attr">"10.0.1.0/24"</span>, <span class="hljs-attr">"10.0.2.0/24"</span>]
}

variable <span class="hljs-string">"availability_zones"</span> {
  description = <span class="hljs-attr">"Availability zones"</span>
  type        = list(string)
  default     = [<span class="hljs-attr">"us-east-1a"</span>, <span class="hljs-attr">"us-east-1b"</span>]
}

variable <span class="hljs-string">"enable_dns_hostnames"</span> {
  description = <span class="hljs-attr">"Enable DNS hostnames in VPC"</span>
  type        = bool
  default     = true
}

variable <span class="hljs-string">"common_tags"</span> {
  description = <span class="hljs-attr">"Common tags for all resources"</span>
  type        = map(string)
  default = {
    ManagedBy = <span class="hljs-attr">"Terraform"</span>
  }
}
</code></pre>
<h3 id="heading-step-3-create-maintfhttpmaintf"><strong>Step 3: Create</strong> <a target="_blank" href="http://main.tf"><strong>main.tf</strong></a></h3>
<pre><code class="lang-json"># main.tf

terraform {
  required_version = <span class="hljs-attr">"&gt;= 1.0"</span>

  required_providers {
    aws = {
      source  = <span class="hljs-attr">"hashicorp/aws"</span>
      version = <span class="hljs-attr">"~&gt; 5.0"</span>
    }
  }
}

provider <span class="hljs-string">"aws"</span> {
  region = var.aws_region

  default_tags {
    tags = var.common_tags
  }
}

# VPC
resource <span class="hljs-string">"aws_vpc"</span> <span class="hljs-string">"main"</span> {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = var.enable_dns_hostnames
  enable_dns_support   = true

  tags = {
    Name        = <span class="hljs-attr">"${var.project_name}-${var.environment}-vpc"</span>
    Environment = var.environment
  }
}

# Internet Gateway
resource <span class="hljs-string">"aws_internet_gateway"</span> <span class="hljs-string">"main"</span> {
  vpc_id = aws_vpc.main.id

  tags = {
    Name        = <span class="hljs-attr">"${var.project_name}-${var.environment}-igw"</span>
    Environment = var.environment
  }
}

# Public Subnets
resource <span class="hljs-string">"aws_subnet"</span> <span class="hljs-string">"public"</span> {
  count = length(var.public_subnet_cidrs)

  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name        = <span class="hljs-attr">"${var.project_name}-${var.environment}-public-subnet-${count.index + 1}"</span>
    Environment = var.environment
    Type        = <span class="hljs-attr">"Public"</span>
  }
}

# Route Table
resource <span class="hljs-string">"aws_route_table"</span> <span class="hljs-string">"public"</span> {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = <span class="hljs-attr">"0.0.0.0/0"</span>
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name        = <span class="hljs-attr">"${var.project_name}-${var.environment}-public-rt"</span>
    Environment = var.environment
  }
}

# Route Table Associations
resource <span class="hljs-string">"aws_route_table_association"</span> <span class="hljs-string">"public"</span> {
  count = length(var.public_subnet_cidrs)

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource <span class="hljs-string">"aws_security_group"</span> <span class="hljs-string">"web"</span> {
  name        = <span class="hljs-attr">"${var.project_name}-${var.environment}-web-sg"</span>
  description = <span class="hljs-attr">"Security group for web servers"</span>
  vpc_id      = aws_vpc.main.id

  ingress {
    description = <span class="hljs-attr">"HTTP from anywhere"</span>
    from_port   = 80
    to_port     = 80
    protocol    = <span class="hljs-attr">"tcp"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  ingress {
    description = <span class="hljs-attr">"HTTPS from anywhere"</span>
    from_port   = 443
    to_port     = 443
    protocol    = <span class="hljs-attr">"tcp"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  egress {
    description = <span class="hljs-attr">"All traffic outbound"</span>
    from_port   = 0
    to_port     = 0
    protocol    = <span class="hljs-attr">"-1"</span>
    cidr_blocks = [<span class="hljs-attr">"0.0.0.0/0"</span>]
  }

  tags = {
    Name        = <span class="hljs-attr">"${var.project_name}-${var.environment}-web-sg"</span>
    Environment = var.environment
  }
}
</code></pre>
<h3 id="heading-step-4-create-outputstfhttpoutputstf"><strong>Step 4: Create</strong> <a target="_blank" href="http://outputs.tf"><strong>outputs.tf</strong></a></h3>
<pre><code class="lang-json"># outputs.tf

output <span class="hljs-string">"vpc_id"</span> {
  description = <span class="hljs-attr">"ID of the VPC"</span>
  value       = aws_vpc.main.id
}

output <span class="hljs-string">"vpc_cidr"</span> {
  description = <span class="hljs-attr">"CIDR block of the VPC"</span>
  value       = aws_vpc.main.cidr_block
}

output <span class="hljs-string">"public_subnet_ids"</span> {
  description = <span class="hljs-attr">"IDs of public subnets"</span>
  value       = aws_subnet.public[*].id
}

output <span class="hljs-string">"public_subnet_cidrs"</span> {
  description = <span class="hljs-attr">"CIDR blocks of public subnets"</span>
  value       = aws_subnet.public[*].cidr_block
}

output <span class="hljs-string">"internet_gateway_id"</span> {
  description = <span class="hljs-attr">"ID of the Internet Gateway"</span>
  value       = aws_internet_gateway.main.id
}

output <span class="hljs-string">"web_security_group_id"</span> {
  description = <span class="hljs-attr">"ID of the web security group"</span>
  value       = aws_security_group.web.id
}

output <span class="hljs-string">"availability_zones"</span> {
  description = <span class="hljs-attr">"Availability zones used"</span>
  value       = aws_subnet.public[*].availability_zone
}

output <span class="hljs-string">"environment"</span> {
  description = <span class="hljs-attr">"Environment name"</span>
  value       = var.environment
}
</code></pre>
<h3 id="heading-step-5-create-terraformtfvars"><strong>Step 5: Create terraform.tfvars</strong></h3>
<pre><code class="lang-json"># terraform.tfvars

environment          = <span class="hljs-string">"dev"</span>
project_name         = <span class="hljs-string">"myapp"</span>
vpc_cidr             = <span class="hljs-string">"10.0.0.0/16"</span>
public_subnet_cidrs  = [<span class="hljs-string">"10.0.1.0/24"</span>, <span class="hljs-string">"10.0.2.0/24"</span>]
availability_zones   = [<span class="hljs-string">"us-east-1a"</span>, <span class="hljs-string">"us-east-1b"</span>]
enable_dns_hostnames = <span class="hljs-literal">true</span>

common_tags = {
  ManagedBy = <span class="hljs-attr">"Terraform"</span>
  Owner     = <span class="hljs-attr">"DevOps Team"</span>
  Purpose   = <span class="hljs-attr">"Learning"</span>
}
</code></pre>
<h3 id="heading-step-6-initialize-and-validate"><strong>Step 6: Initialize and Validate</strong></h3>
<pre><code class="lang-bash">terraform init
terraform fmt
terraform validate
</code></pre>
<h3 id="heading-step-7-plan-with-different-environments"><strong>Step 7: Plan with Different Environments</strong></h3>
<p><strong>Development:</strong></p>
<pre><code class="lang-bash">terraform plan
</code></pre>
<p><strong>Staging (create staging.tfvars):</strong></p>
<pre><code class="lang-json"># staging.tfvars
environment = <span class="hljs-string">"staging"</span>
vpc_cidr    = <span class="hljs-string">"10.1.0.0/16"</span>
public_subnet_cidrs = [<span class="hljs-string">"10.1.1.0/24"</span>, <span class="hljs-string">"10.1.2.0/24"</span>]
</code></pre>
<pre><code class="lang-bash">terraform plan -var-file=<span class="hljs-string">"staging.tfvars"</span>
</code></pre>
<h3 id="heading-step-8-apply-configuration"><strong>Step 8: Apply Configuration</strong></h3>
<pre><code class="lang-bash">terraform apply
</code></pre>
<p>Type <code>yes</code> when prompted.</p>
<h3 id="heading-step-9-view-outputs"><strong>Step 9: View Outputs</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># All outputs</span>
terraform output
<span class="hljs-comment"># Specific output</span>
terraform output vpc_id
<span class="hljs-comment"># JSON format</span>
terraform output -json &gt; infrastructure.json
</code></pre>
<p><strong>Expected output:</strong></p>
<pre><code class="lang-json">availability_zones = [
  <span class="hljs-string">"us-east-1a"</span>,
  <span class="hljs-string">"us-east-1b"</span>,
]
environment = <span class="hljs-string">"dev"</span>
internet_gateway_id = <span class="hljs-string">"igw-xxxxx"</span>
public_subnet_cidrs = [
  <span class="hljs-string">"10.0.1.0/24"</span>,
  <span class="hljs-string">"10.0.2.0/24"</span>,
]
public_subnet_ids = [
  <span class="hljs-string">"subnet-xxxxx"</span>,
  <span class="hljs-string">"subnet-yyyyy"</span>,
]
vpc_cidr = <span class="hljs-string">"10.0.0.0/16"</span>
vpc_id = <span class="hljs-string">"vpc-xxxxx"</span>
web_security_group_id = <span class="hljs-string">"sg-xxxxx"</span>
</code></pre>
<h3 id="heading-step-10-test-variable-validation"><strong>Step 10: Test Variable Validation</strong></h3>
<p>Try an invalid environment:</p>
<pre><code class="lang-bash">terraform apply -var=<span class="hljs-string">"environment=production"</span>
</code></pre>
<p>You’ll get an error:</p>
<pre><code class="lang-json">Error: Invalid value for variable

Environment must be dev, staging, or prod.
</code></pre>
<h3 id="heading-step-11-clean-up"><strong>Step 11: Clean Up</strong></h3>
<pre><code class="lang-bash">terraform destroy
</code></pre>
<h2 id="heading-variable-best-practices">📋 Variable Best Practices</h2>
<h3 id="heading-do">✅ <strong>DO:</strong></h3>
<ol>
<li><p><strong>Use descriptive names</strong></p>
<pre><code class="lang-json"> variable <span class="hljs-string">"vpc_cidr_block"</span> {}  # ✅ Clear
 variable <span class="hljs-string">"cidr"</span> {}             # ❌ Vague
</code></pre>
</li>
<li><p><strong>Always add descriptions</strong></p>
<pre><code class="lang-json"> variable <span class="hljs-string">"region"</span> {
   description = <span class="hljs-attr">"AWS region for all resources"</span>
   type        = string
 }
</code></pre>
</li>
<li><p><strong>Specify types explicitly</strong></p>
<pre><code class="lang-json"> variable <span class="hljs-string">"count"</span> {
   type = number  # ✅ Explicit
 }
</code></pre>
</li>
<li><p><strong>Use validation when appropriate</strong></p>
<pre><code class="lang-json"> validation {
   condition     = var.instance_count &gt; 0
   error_message = <span class="hljs-attr">"Must be positive."</span>
 }
</code></pre>
</li>
<li><p><strong>Group-related variables</strong></p>
<pre><code class="lang-json"> # Network variables
 variable <span class="hljs-string">"vpc_cidr"</span> {}
 variable <span class="hljs-string">"subnet_cidrs"</span> {}

 # Compute variables
 variable <span class="hljs-string">"instance_type"</span> {}
 variable <span class="hljs-string">"instance_count"</span> {}
</code></pre>
</li>
</ol>
<h3 id="heading-dont">❌ <strong>DON’T:</strong></h3>
<ol>
<li><p>Don’t hardcode sensitive values</p>
</li>
<li><p>Don’t use overly complex variable structures</p>
</li>
<li><p>Don’t skip documentation</p>
</li>
<li><p>Don’t use inconsistent naming</p>
</li>
</ol>
<h2 id="heading-summary">📝 Summary</h2>
<p>Today you learned:</p>
<ul>
<li><p>✅ Input variables and their importance</p>
</li>
<li><p>✅ All variable types (string, number, bool, list, map, object)</p>
</li>
<li><p>✅ Variable definition precedence</p>
</li>
<li><p>✅ Multiple ways to set variable values</p>
</li>
<li><p>✅ Variable validation</p>
</li>
<li><p>✅ Output values and their uses</p>
</li>
<li><p>✅ Built parameterized, reusable infrastructure</p>
</li>
</ul>
<h2 id="heading-tomorrows-preview">🚀 Tomorrow’s Preview</h2>
<p><strong>Day 5: Resource Dependencies &amp; Data Sources</strong></p>
<p>Tomorrow we’ll:</p>
<ul>
<li><p>Understand implicit vs explicit dependencies</p>
</li>
<li><p>Learn about <code>depends_on</code></p>
</li>
<li><p>Master data sources to query existing infrastructure</p>
</li>
<li><p>Reference external resources</p>
</li>
<li><p>Build complex interdependent infrastructure</p>
</li>
</ul>
<h2 id="heading-challenge-exercise">💭 Challenge Exercise</h2>
<p>Create a <strong>production.tfvars</strong> file that:</p>
<ol>
<li><p>Uses a different VPC CIDR (<code>172.16.0.0/16</code>)</p>
</li>
<li><p>Creates 3 subnets instead of 2</p>
</li>
<li><p>Sets environment to “prod”</p>
</li>
<li><p>Adds a custom tag “CostCenter = Production”</p>
</li>
</ol>
<p>Then apply it:</p>
<pre><code class="lang-bash">terraform apply -var-file=<span class="hljs-string">"production.tfvars"</span>
</code></pre>
<hr />
<p><strong>Happy Learning! 🎉</strong></p>
<blockquote>
<p><strong><em>Thanks For Reading, Follow Me For More and subscribe</em></strong> <a target="_blank" href="https://www.youtube.com/channel/UCWQ_nnU_fdvqfMyyn-RCu_A"><strong><em>youtube</em></strong></a> <strong><em>channel for the recap videos</em></strong></p>
<p><strong><em>Have a great day!..</em></strong></p>
</blockquote>
<p><a target="_blank" href="https://stackopsdiary.hashnode.dev/day-3-understanding-providers-and-aws-setup">← Day 3: Understanding Providers</a> | <a target="_blank" href="https://stackopsdiary.hashnode.dev/day-5-resource-dependencies-and-data-sources">Day 5: Dependencies &amp; Data Sources →</a></p>
<hr />
<p><em>Remember: Variables make your Terraform code reusable, flexible, and maintainable!</em></p>
]]></content:encoded></item></channel></rss>