DEV Community

Timevolt
Timevolt

Posted on

Infrastructure as Code: Terraform and CloudFormation – The Fellowship of the Cloud

The Quest Begins (The "Why")

Honestly, I used to feel like I was stuck in a never‑ending boss fight every time our team needed to spin up a new environment. We’d click through the AWS console, copy‑paste CLI commands, and pray that the VPC, subnets, security groups, and IAM roles all matched what we had in staging. One missed tag or a typo in a CIDR block and boom—our QA environment would look like a scene from The Matrix where Neo dodges bullets, except we were the ones getting hit. After a particularly painful outage caused by a drifted security group, I thought, “There has to be a better way.” That’s when I stumbled onto Infrastructure as Code (IaC) and realized I could treat my cloud resources like code—version‑reviewed, testable, and repeatable.

The Revelation (The Insight)

The magic click came when I saw that IaC isn’t just about writing JSON or HCL; it’s about defining the desired state of your world and letting a tool figure out how to get there. Terraform and CloudFormation both let you declare resources, but they each bring their own flavor to the party. Terraform feels like picking up a versatile sword—you can swing it across AWS, Azure, GCP, and even on‑prem with the same blade. CloudFormation, on the other hand, is the trusty shield forged specifically for AWS; it knows the nuances of every service and speaks AWS’s native language fluently.

What blew my mind was how a single file could spin up an entire VPC with public and private subnets, an RDS instance, and an IAM role for Lambda—all while being stored in Git. No more “works on my machine” excuses; if it applies in the repo, it applies in the cloud. And the best part? You can preview changes with a terraform plan or a CloudFormation changeset before you ever hit “apply.” It’s like having a map that shows you every trap before you step on it.

Wielding the Power (Code & Examples)

Before: The Manual Struggle

Here’s a snippet of what a typical “click‑ops” run‑book looked like for a simple web tier:

# Create VPC
aws ec2 create-vpc --cidr-block 10.0.0.0/16
VPC_ID=$(aws ec2 describe-vpcs --filters Name=cidr,Values=10.0.0.0/16 --query 'Vpcs[0].VpcId' --output text)

# Internet Gateway
aws ec2 create-internet-gateway
IGW_ID=$(aws ec2 describe-internet-gateways --query 'InternetGateways[0].InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID

# Public Subnet
aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.1.0/24 --availability-zone us-east-1a
PUB_SUBNET=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC_ID Name=cidr,Values=10.0.1.0/24 --query 'Subnets[0].SubnetId' --output text)

# Route Table for Public Subnet
aws ec2 create-route-table --vpc-id $VPC_ID
RTB_ID=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=$VPC_ID --query 'RouteTables[0].RouteTableId' --output text)
aws ec2 associate-route-table --subnet-id $PUB_SUBNET --route-table-id $RTB_ID
aws ec2 create-route --route-table-id $RTB_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
Enter fullscreen mode Exit fullscreen mode

You can already see the pitfalls: hard‑coded AZs, no tagging, easy to forget a step, and zero idempotency. If you ran this twice, you’d get errors or duplicate resources.

After: Terraform (the Cross‑Cloud Sword)

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

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = { Name = "fellowship-vpc" }
}

resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "fellowship-igw" }
}

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"
  tags              = { Name = "fellowship-public" }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "fellowship-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.gw.id
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}
Enter fullscreen mode Exit fullscreen mode

What changed?

  • Declarative: describe what you want, not how to get it.
  • Idempotent: run terraform apply again and nothing changes if the state matches.
  • Version‑controlled: every tweak lives in Git, reviewable via PR.
  • Tagging baked in: no forgotten cost‑allocation tags.

Common trap #1 – Forgetting to lock the provider version.

If you omit required_version or required_providers, a future provider release might introduce breaking changes. Always pin:

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Common trap #2 – Hard‑coding AZs.

Hard‑coding a single AZ can cause failures if that zone experiences issues. Use data sources or aws_availability_zones to pick dynamically:

data "aws_availability_zones" "available" {}

resource "aws_subnet" "public" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = { Name = "fellowship-public-${count.index}" }
}
Enter fullscreen mode Exit fullscreen mode

After: CloudFormation (the AWS‑Forged Shield)

If you prefer to stay strictly within AWS and want native drift detection, CloudFormation shines:

AWSTemplateFormatVersion: '2010-09-09'
Description: >-
  VPC with public subnet – the Fellowship of the Cloud

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
        - Key: Name
          Value: fellowship-vpc

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: fellowship-igw

  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: us-east-1a
      Tags:
        - Key: Name
          Value: fellowship-public

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: fellowship-rt

  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
Enter fullscreen mode Exit fullscreen mode

What’s nice?

  • Built‑in drift detection: run aws cloudformation detect-stack-drift and get notified if someone manually changes a resource.
  • Stack policies: protect critical resources from accidental updates or deletes.
  • Seamless integration with AWS CodePipeline for CI/CD.

Common trap #1 – Forgetting the DependsOn attribute.

Without it, CloudFormation might try to create the route before the Internet Gateway is attached, leading to a failure. Always declare explicit dependencies when the order matters.

Common trap #2 – Using Ref on a resource that returns a complex object.

For example, !Ref PublicSubnet gives the Subnet ID, which is fine, but if you try to treat it as a CIDR block you’ll get a validation error. Keep track of what each intrinsic function returns.

Why This New Power Matters

Now that I’ve got Terraform and CloudFormation in my toolkit, I feel like I’ve leveled up from a squire to a knight‑commander. I can spin up identical dev, test, and prod environments in minutes, not hours. I can review infrastructure changes alongside application code in the same pull request, giving the whole team visibility and confidence. And when something does go wrong, the state file or stack history tells me exactly what drifted—no more guessing games.

The real win? Repeatability. Whether I’m standing up a tiny demo VPC or a multi‑region, multi‑service architecture, the same definitions apply. It’s like having a reusable spellbook; I just flip to the right page and cast.

Your Turn – Embark on Your Own Quest

Ready to ditch the manual clicks and start treating your infra like code? Here’s a challenge: pick a simple piece of your current environment—a security group, an S3 bucket, or a Lambda—and rewrite it in either Terraform or CloudFormation. Commit it, open a PR, and watch your teammates review it alongside your feature branch. When it passes, you’ll have just slain the “configuration drift” dragon.

What’s the first resource you’ll IaC‑ify? Drop a comment below—I’d love to hear about your victories (and the occasional “oops, I forgot a tag” moments) as you forge your own Fellowship of the Cloud! 🚀

Top comments (0)