Automating AWS Infrastructure Using Terraform: VPC, EC2, S3, and Load Balancer Setup

Saminder SinghSaminder Singh
4 min read

As part of my DevOps learning journey, I recently worked on a project to automate a basic but complete AWS infrastructure using Terraform. This was a hands-on experience where I provisioned and configured networking, compute, storage, and load balancing resources using Infrastructure as Code (IaC). Here's a breakdown of what I built, how it works, the challenges I faced, and the lessons learned.


Project Overview

The goal was to provision the following AWS components using Terraform:

  • A VPC with two public subnets in different Availability Zones

  • An Internet Gateway and Route Table

  • A Security Group with HTTP and SSH access

  • Two EC2 instances with user-data scripts

  • An S3 bucket with full access granted to EC2 via IAM Role

  • An Application Load Balancer (ALB) with target groups and listeners


Infrastructure Details

1. VPC and Subnets

I created a custom VPC and two subnets in different AZs:

resource "aws_vpc" "myvpc" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "sub1" {
  vpc_id                  = aws_vpc.myvpc.id
  cidr_block              = "10.0.0.0/24"
  availability_zone       = "us-east-1a"
  map_public_ip_on_launch = true
}

resource "aws_subnet" "sub2" {
  vpc_id                  = aws_vpc.myvpc.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-east-1b"
  map_public_ip_on_launch = true
}

2. Internet Gateway and Route Table

To enable internet access:

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.myvpc.id
}

resource "aws_route_table" "RT" {
  vpc_id = aws_vpc.myvpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
}

resource "aws_route_table_association" "rta1" {
  subnet_id      = aws_subnet.sub1.id
  route_table_id = aws_route_table.RT.id
}

resource "aws_route_table_association" "rta2" {
  subnet_id      = aws_subnet.sub2.id
  route_table_id = aws_route_table.RT.id
}

3. Security Group

Allowing inbound traffic:

resource "aws_security_group" "webSg" {
  name   = "web"
  vpc_id = aws_vpc.myvpc.id

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

  ingress {
    from_port   = 22
    to_port     = 22
    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"]
  }
}

4. IAM Role for S3 Access

Attaching an S3 access role to EC2:

resource "aws_iam_role" "ec2_role" {
  name = "ec2_s3_role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect = "Allow",
      Principal = { Service = "ec2.amazonaws.com" },
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "ec2_role_attachment" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
  role       = aws_iam_role.ec2_role.name
}

resource "aws_iam_instance_profile" "ec2_profile" {
  name = "ec2_s3_profile"
  role = aws_iam_role.ec2_role.name
}

5. EC2 Instances

Launching two EC2 instances in each subnet:

resource "aws_instance" "webservers1" {
  ami                    = "ami-020cba7c55df1f615"
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.sub1.id
  vpc_security_group_ids = [aws_security_group.webSg.id]
  user_data              = base64encode(file("userdata.sh"))
  iam_instance_profile   = aws_iam_instance_profile.ec2_profile.name
}

resource "aws_instance" "webservers2" {
  ami                    = "ami-020cba7c55df1f615"
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.sub2.id
  vpc_security_group_ids = [aws_security_group.webSg.id]
  user_data              = base64encode(file("userdata1.sh"))
  iam_instance_profile   = aws_iam_instance_profile.ec2_profile.name
}

6. Application Load Balancer

Distributing traffic evenly:

resource "aws_lb" "myalb" {
  name               = "myalb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.webSg.id]
  subnets            = [aws_subnet.sub1.id, aws_subnet.sub2.id]
}

resource "aws_lb_target_group" "tg" {
  name     = "myTG"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.myvpc.id

  health_check {
    path = "/"
    port = "traffic-port"
  }
}

resource "aws_lb_target_group_attachment" "attach1" {
  target_group_arn = aws_lb_target_group.tg.arn
  target_id        = aws_instance.webservers1.id
  port             = 80
}

resource "aws_lb_target_group_attachment" "attach2" {
  target_group_arn = aws_lb_target_group.tg.arn
  target_id        = aws_instance.webservers2.id
  port             = 80
}

resource "aws_lb_listener" "listener" {
  load_balancer_arn = aws_lb.myalb.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.tg.arn
  }
}

Challenges I Faced

  • IAM Role Confusion: It took me time to properly link the IAM role to EC2 with the correct trust relationship and instance profile. This blog helped: Mahira Technology on IAM + EC2 + S3

  • ALB Configuration: Understanding the flow between ALB, listener, target groups, and EC2 was tricky at first. I referred to the Terraform AWS Load Balancer Docs


Key Takeaways

  • Terraform makes AWS resource provisioning scalable and repeatable

  • Load balancers distribute traffic and improve availability

  • IAM roles are critical for secure access to other AWS services like S3

  • Having a strong understanding of AWS networking (VPCs, Subnets, Routing) is essential


Final Thoughts

This project was an incredible learning experience and helped me gain confidence in working with Terraform and AWS. It was a practical way to understand how real infrastructure is set up in production.

If you're also exploring DevOps, Cloud, or Infrastructure as Code, I highly recommend trying something similar. Want help setting up your own? Feel free to reach out!


0
Subscribe to my newsletter

Read articles from Saminder Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Saminder Singh
Saminder Singh