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)
Write .tf configuration
Terraform
Translates to API calls
Cloud Provider
Creates real resources
The Problem It Solves
# 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
# 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
terraform init One-time setup
terraform plan Safe preview
terraform apply Make it real
terraform destroy Tear it down
Code Examples
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
} 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"]
}
} 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" 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.
❌ 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
Infrastructure as Code
Treat infrastructure like application code — version it, review it, test it, automate it.
Declarative, not imperative
You describe the end state you want. Terraform figures out how to get there.
State is sacred
Protect your state file. Use remote backends with locking. Never edit manually.
Plan before apply
Always run terraform plan first. Review changes. Then apply with confidence.
Start simple, grow modular
Begin with flat configs. Extract modules when patterns emerge. Don't over-engineer early.