The Quest Begins (The "Why")
Honestly, I was tired of playing “guess the configuration” every time I spun up a new environment. One day I clicked “Apply” in the AWS console, watched a VPC pop up, then realized three weeks later that the same VPC existed in two accounts with slightly different CIDR blocks. It felt like trying to herd cats while blindfolded. I knew there had to be a better way—something that would let me define my infrastructure once, version‑control it, and apply it everywhere with confidence. That’s when the idea of Infrastructure as Code (IaC) stopped being a buzzword and started looking like the holy grail I’d been chasing.
The Revelation (The Insight)
The magic moment came when I realized IaC isn’t just about writing scripts; it’s about treating your infrastructure like application code. You get peer reviews, automated tests, rollbacks, and the ability to see exactly what changed between releases. Two heavyweight contenders in the AWS world are Terraform and CloudFormation. Both let you declare resources, but they differ in language, ecosystem, and workflow.
Terraform uses its own declarative language (HCL) and works across many providers—AWS, Azure, GCP, you name it. CloudFormation is native to AWS and uses JSON or YAML templates. If you’re all‑in on AWS, CloudFormation feels like a natural extension of the console. If you crave multi‑cloud flexibility or love a rich module registry, Terraform often wins the day.
What blew my mind was how a single file could describe an entire VPC, subnets, security groups, and even an EC2 fleet, and then a single command could bring it to life—or tear it down—safely. It felt like Neo seeing the Matrix code for the first time: everything snapped into focus, and I could manipulate reality with a few keystrokes.
Wielding the Power (Code & Examples)
The Struggle: Manual Click‑Ops
Before IaC, I’d spin up a web tier like this (pseudo‑steps, not actual code):
- Open AWS Console → VPC → Create VPC (10.0.0.0/16)
- Create two subnets (public/private) in each AZ
- Attach an Internet Gateway, configure route tables
- Create a security group, open ports 80/443
- Launch an Auto Scaling Group with a launch configuration
- Tag everything for billing
Repeat steps 1‑6 for staging, prod, and each new account. Miss a tag? Forget a route? You’re debugging at 2 a.m.
The Victory: Terraform
Here’s a compact Terraform example that does the same thing in ~30 lines. Save it as main.tf.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
variable "region" {
description = "AWS region to deploy into"
type = string
default = "us-east-1"
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "fellowship-vpc"
Env = var.env
}
}
# Internet Gateway
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "fellowship-igw"
}
}
# Public Subnet (example: one AZ)
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "${var.region}a"
tags = {
Name = "fellowship-public"
}
}
# Route Table for public subnet
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "fellowship-rt-public"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# Security Group (allow HTTP/HTTPS)
resource "aws_security_group" "web" {
name = "fellowship-sg-web"
description = "Allow web traffic"
vpc_id = aws_vpc.main.id
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"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "fellowship-sg-web"
}
}
# Launch Template for ASG
resource "aws_launch_template" "web_lt" {
name_prefix = "fellowship-web-"
image_id = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
network_interfaces {
security_groups = [aws_security_group.web.id]
associate_public_ip_address = true
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "fellowship-web"
}
}
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "web_asg" {
name = "fellowship-asg"
max_size = 3
min_size = 1
desired_capacity = 2
vpc_zone_identifier = [aws_subnet.public.id]
launch_template {
id = aws_launch_template.web_lt.id
version = "$Latest"
}
tag {
key = "Name"
value = "fellowship-asg"
propagate_at_launch = true
}
}
What just happened?
- The
terraformblock locks the provider version—no surprise upgrades breaking your stack. - Variables let you reuse the same code for
dev,staging,prodby simply changing-var env=prod. - Resources are declared declaratively; Terraform figures out the correct order (VPC → IGW → Subnet → Route Table → SG → Launch Template → ASG).
- Running
terraform init && terraform applycreates everything in seconds.terraform destroytears it down cleanly.
Common Traps (The “Monsters” to Avoid)
-
Hard‑coding IDs – If you copy‑paste a subnet ID from the console into your code, you’ll lose portability. Always reference resources by their Terraform attributes (
aws_subnet.public.id). -
Missing State Locking – Terraform state is the source of truth. Without a remote backend (S3 + DynamoDB lock), two teammates running
applysimultaneously can corrupt state. Set up a backend early:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "fellowship/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
}
}
The CloudFormation Counterpart
If you prefer staying purely within AWS, here’s the same VPC + ASG skeleton in YAML (about 45 lines).
AWSTemplateFormatVersion: '2010-09-09'
Description: Fellowship VPC with Web ASG
Parameters:
Env:
Type: String
Default: dev
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
Tags:
- Key: Name
Value: fellowship-vpc
- Key: Env
Value: !Ref Env
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: fellowship-igw
DependsOn: VPC
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '' ]
Tags:
- Key: Name
Value: fellowship-public
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: fellowship-rt-public
PublicRoute:
Type: AWS::EC2::Route
DependsOn: VPCGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
WebSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow web traffic
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: fellowship-sg-web
WebLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
ImageId: !ResolveAWS::SSM::Parameter::Value:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
InstanceType: t3.micro
NetworkInterfaces:
- AssociatePublicIpAddress: true
SecurityGroupIds:
- !Ref WebSG
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Name
Value: fellowship-web
WebASG:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
VPCZoneIdentifier:
- !Ref PublicSubnet
LaunchTemplate:
LaunchTemplateId: !Ref WebLaunchTemplate
Version: '$Latest'
MinSize: '1'
MaxSize: '3'
DesiredCapacity: '2'
Tags:
- Key: Name
Value: fellowship-asg
PropagateAtLaunch: true
Why you might choose one over the other:
- Terraform shines when you need to manage resources across clouds or want access to the massive public module registry (think “one‑click VPC with NAT gateway”).
- CloudFormation feels like a natural extension of the AWS console; changes are visible in the Stacks UI, and you get drift detection out of the box.
Pick the tool that matches your team’s comfort zone, but remember: the real win is treating infrastructure as version‑controlled, testable code.
Why This New Power Matters
Now you can spin up a whole environment for a feature branch, run integration tests against it, and tear it down when the PR merges—all without clicking through a dozen consoles. Imagine delivering a new microservice with a single terraform apply and knowing the exact same definition runs
Top comments (0)