DevOps
Learning Corner
Intermediate 12 min read

Terraform

Infrastructure as Code — define, provision, and manage cloud resources with declarative configuration

Think of it as a

Blueprint

for your entire cloud infrastructure

The Architect Analogy

Imagine you're an architect. You don't build houses by hand every time — you create blueprints. The blueprint describes exactly what you want, and construction workers follow it to build the real thing. If you need another identical house? Same blueprint, same result.

YOU Developer .tf files TERRAFORM Configuration CLOUD PROVIDER EC2 RDS S3 VPC IAM LB write apply
👷

You (Developer)

Write .tf configuration

📐

Terraform

Translates to API calls

☁️

Cloud Provider

Creates real resources

The Problem It Solves

Manual (ClickOps)
# Monday morning routine...
1. Log into AWS Console
2. Click EC2 → Launch Instance
3. Select AMI... wait, which one?
4. Configure security groups
5. Hope you remember all the settings
6. Repeat 47 more times 😩
7. Document nothing
8. Pray it works in production
  • No version control for infrastructure
  • Can't reproduce environments reliably
  • No audit trail of changes
  • Human error on every click
  • Impossible to review before deploying
Infrastructure as Code
# main.tf - the whole story
resource "aws_instance" "web" {
  count         = 48
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

# git commit -m "Add 48 web servers"
# terraform apply
# ☕ Done.
  • Git history = infrastructure history
  • Same config = same infrastructure
  • Review changes before applying
  • Automate everything
  • Self-documenting infrastructure

Core Concepts

🔌

Providers

Plugins that connect Terraform to cloud platforms (AWS, GCP, Azure, etc.). Each provider knows how to talk to its API.

📦

Resources

The actual infrastructure components you want to create — servers, databases, networks, DNS records, etc.

🧠

State

Terraform's memory — a JSON file tracking what exists in the real world vs. what's in your config.

🎛️

Variables

Parameters that make your config reusable — change the environment, region, or size without rewriting code.

📤

Outputs

Values extracted after apply — IP addresses, URLs, IDs — useful for other tools or modules.

🔍

Data Sources

Read-only queries to fetch existing infrastructure info — AMI IDs, VPC details, DNS zones.

The Workflow

WRITE main.tf variables.tf INIT Download providers PLAN Preview changes APPLY Execute changes INFRA Running! terraform destroy
terraform init

One-time setup

terraform plan

Safe preview

terraform apply

Make it real

terraform destroy

Tear it down

Code Examples

1

Configure the Provider

First, tell Terraform which cloud you're using and how to authenticate.

# providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
  
  # Auth via env vars: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
  # Or use: aws configure
}
2

Define Resources

Resources are the building blocks — each one maps to real infrastructure.

# main.tf - Create a web server
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  
  tags = {
    Name = "web-server"
    Env  = "production"
  }
}

# Create a security group
resource "aws_security_group" "web_sg" {
  name        = "web-sg"
  description = "Allow HTTP traffic"
  
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
3

Use Variables for Reusability

Variables make your config flexible. Define once, use everywhere.

# variables.tf
variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

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

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

# main.tf - Use variables
resource "aws_instance" "web" {
  count         = var.instance_count
  instance_type = var.instance_type
  
  tags = {
    Name = "$${var.environment}-web-$${count.index}"
  }
}

# Apply with: terraform apply -var="environment=prod" -var="instance_count=3"
4

Extract Outputs

Outputs expose values after infrastructure is created — useful for other tools or humans.

# outputs.tf
output "instance_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

output "instance_id" {
  description = "Instance ID"
  value       = aws_instance.web.id
}

output "ssh_command" {
  description = "SSH into the instance"
  value       = "ssh ec2-user@$${aws_instance.web.public_ip}"
}

# After apply:
# instance_ip = "54.123.45.67"
# ssh_command = "ssh ec2-user@54.123.45.67"

Understanding State

⚠️

State is Critical

Terraform state (terraform.tfstate) is how Terraform knows what exists in the real world. Lose it, and Terraform loses its memory. Corrupt it, and things break.

YOUR CONFIG main.tf "I want 3 servers" STATE FILE terraform.tfstate "There are 2 servers" "IDs: i-abc, i-def" REAL WORLD AWS/GCP/Azure Actual resources compare sync Plan: +1 to add (3 − 2 = 1 new server)

❌ Don't

  • Commit terraform.tfstate to git
  • Edit state files manually
  • Share state via Dropbox/Drive
  • Delete state without importing

✅ Do

  • Use remote state (S3, GCS, Azure Blob)
  • Enable state locking
  • Back up state regularly
  • Use terraform import for existing resources

Advanced Concepts

📦

Modules

Reusable, shareable packages of Terraform configuration. Like functions for infrastructure.

# Use a module from the Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"
  
  name = "my-vpc"
  cidr = "10.0.0.0/16"
  
  azs             = ["us-east-1a", "us-east-1b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
}

# Reference module outputs
resource "aws_instance" "web" {
  subnet_id = module.vpc.private_subnets[0]
}
☁️

Remote State

Store state in the cloud for team collaboration and safety. Supports locking to prevent conflicts.

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"  # For state locking
  }
}
🗂️

Workspaces

Manage multiple environments (dev, staging, prod) with the same configuration but separate state.

# CLI workspace commands
$ terraform workspace list
  default
* dev
  staging
  prod

$ terraform workspace select prod
Switched to workspace "prod".

$ terraform workspace new feature-x
Created and switched to workspace "feature-x"!

# Use workspace name in your config
resource "aws_instance" "web" {
  tags = {
    Environment = terraform.workspace  # "dev", "staging", or "prod"
  }
}

# Different instance sizes per environment
locals {
  instance_type = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.large"
  }
}

resource "aws_instance" "web" {
  instance_type = local.instance_type[terraform.workspace]
}

Why Use Terraform?

🔄

Reproducibility

Same config = same infrastructure. Every time. No more "works on my cloud".

📝

Version Control

Git history for infrastructure. Who changed what, when, and why.

👥

Collaboration

Review infrastructure changes in PRs. No more "I clicked something in the console".

☁️

Multi-Cloud

One language for AWS, GCP, Azure, and 3000+ providers. Learn once, deploy anywhere.

🤖

Automation

CI/CD for infrastructure. Push code, get infrastructure. No humans in the loop.

📊

Drift Detection

Terraform shows when reality doesn't match your config. No more surprise changes.

When to Use Terraform

Use when:

  • Managing cloud infrastructure (any scale)
  • You need multiple environments (dev/staging/prod)
  • Team collaboration on infrastructure
  • Compliance requires audit trails
  • Disaster recovery / reproducibility matters
  • Multi-cloud or hybrid deployments

⚠️ Consider alternatives if:

  • Quick one-off experiments (just use console)
  • Serverless-only apps (SAM, Serverless Framework)
  • Kubernetes-native (Helm, Pulumi, Crossplane)
  • Single cloud, want native tooling (CloudFormation, Deployment Manager)
  • Team has no IaC experience (steep learning curve)

Trade-offs

Pros

  • Declarative — describe what, not how
  • Massive provider ecosystem (3000+)
  • Strong community and documentation
  • HCL is readable and learnable
  • Excellent plan/preview before changes
  • State enables drift detection

Cons

  • State management complexity
  • HCL has limitations (no real loops, limited logic)
  • Provider bugs can block upgrades
  • Large refactors require state surgery
  • Learning curve for teams new to IaC
  • Secrets in state (needs encryption)

Key Takeaways

1

Infrastructure as Code

Treat infrastructure like application code — version it, review it, test it, automate it.

2

Declarative, not imperative

You describe the end state you want. Terraform figures out how to get there.

3

State is sacred

Protect your state file. Use remote backends with locking. Never edit manually.

4

Plan before apply

Always run terraform plan first. Review changes. Then apply with confidence.

5

Start simple, grow modular

Begin with flat configs. Extract modules when patterns emerge. Don't over-engineer early.