Building a Scalable 3-Tier Architecture on AWS Using Terraform: A Modular Approach

 In this project, we will design and implement a scalable three-tier architecture on AWS using Terraform. The setup follows a modular approach, organizing infrastructure into separate layers: Core, Web, App, and Database. This ensures better manageability, reusability, and security while deploying cloud resources. By the end, you will have a fully automated, infrastructure-as-code (IaC) solution for hosting applications on AWS.

GitHub link: https://github.com/Consultantsrihari/3-Tier-Architecture-on-AWS-Using-Terraform.git

Prerequisites

Before starting this project, ensure you have the following:

✅ AWS Account — To provision cloud resources using Terraform.
✅ Terraform Installed — Download and install Terraform on your local machine.
✅ AWS CLI Installed & Configured — Install AWS CLI and run aws configure to set up credentials.
✅ IAM User with Required Permissions – Ensure your IAM user has permissions to create and manage VPCs, EC2, RDS, ALB, and other AWS resources.
✅ Basic Knowledge of Terraform – Familiarity with writing .tf files, providers, modules, and variables.
✅ Code Editor (VS Code Recommended) – Use VS Code with the Terraform extension for syntax highlighting and better development experience.
✅ Git Installed (Optional) – For version control and managing Terraform code in a GitHub repository.

Architecture Overview

We will deploy a three-tier architecture consisting of the following:

  • VPC (Core Layer): Contains public and private subnets.
  • Public Subnets: Bastion Host & NAT Gateway.
  • Private Subnets: Web/Application servers (EC2 instances) and Database (Amazon RDS).
  • Load Balancer: ALB to distribute traffic.
  • Security Groups: Restricted access at different layers.

Project Structure:-

Root Module (main.tf)

module "core" {
source = "./modules/core"

vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.3.0/24", "10.0.4.0/24"]
db_subnet_cidrs = ["10.0.5.0/24", "10.0.6.0/24"]
azs = ["us-east-1a", "us-east-1b"]
}

module "web" {
source = "./modules/web"

public_subnet_ids = module.core.public_subnet_ids
web_alb_sg_id = module.core.web_alb_sg_id
web_instance_sg_id = module.core.web_instance_sg_id
web_ami = "ami-0c55b159cbfafe1f0"
web_instance_type = "t2.micro"
}

module "app" {
source = "./modules/app"

private_subnet_ids = module.core.private_subnet_ids
app_alb_sg_id = module.core.app_alb_sg_id
app_instance_sg_id = module.core.app_instance_sg_id
app_ami = "ami-0c55b159cbfafe1f0"
app_instance_type = "t2.micro"
}

module "database" {
source = "./modules/database"

db_subnet_ids = module.core.db_subnet_ids
db_sg_id = module.core.db_sg_id
db_name = "mydb"
db_user = "admin"
db_password = "securepassword123"
}

Security Group Setup

sg.tf

# Web ALB Security Group
resource "aws_security_group" "web_alb" {
name = "web-alb-sg"
description = "Allow HTTP inbound traffic"
vpc_id = aws_vpc.main.id

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

# Database Security Group
resource "aws_security_group" "database" {
name = "db-sg"
description = "Allow MySQL access from app tier"
vpc_id = aws_vpc.main.id

ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [module.core.app_instance_sg_id]
}
}

1. Core Network Module (modules/core)

main.tf

resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "3tier-vpc"
}
}

# Public Subnets
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.azs[count.index]
tags = {
Name = "public-subnet-${count.index}"
}
}

# Private Subnets (App Tier)
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.azs[count.index]
tags = {
Name = "private-subnet-${count.index}"
}
}

# Database Subnets
resource "aws_subnet" "database" {
count = length(var.db_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.db_subnet_cidrs[count.index]
availability_zone = var.azs[count.index]
tags = {
Name = "db-subnet-${count.index}"
}
}

# Internet Gateway
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
}

# NAT Gateway
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
}

resource "aws_eip" "nat" {
vpc = true
}

# Route Tables
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
}
}

resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
}

# Subnet Associations
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
count = length(var.private_subnet_cidrs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}

2. Web Tier Module (modules/web)

main.tf

resource "aws_lb" "web" {
name = "web-alb"
internal = false
load_balancer_type = "application"
security_groups = [var.web_alb_sg_id]
subnets = var.public_subnet_ids
}

resource "aws_launch_template" "web" {
name_prefix = "web-"
image_id = var.web_ami
instance_type = var.web_instance_type
key_name = var.key_name

network_interfaces {
security_groups = [var.web_instance_sg_id]
}

user_data = base64encode(<<-EOF
#!/bin/bash
yum install -y nginx
systemctl start nginx
EOF
)
}

resource "aws_autoscaling_group" "web" {
desired_capacity = 2
max_size = 4
min_size = 2
vpc_zone_identifier = var.public_subnet_ids

launch_template {
id = aws_launch_template.web.id
version = "$Latest"
}
}

3. Application Tier Module (modules/app)

main.tf

resource "aws_lb" "app" {
name = "app-alb"
internal = true
load_balancer_type = "application"
security_groups = [var.app_alb_sg_id]
subnets = var.private_subnet_ids
}

resource "aws_launch_template" "app" {
name_prefix = "app-"
image_id = var.app_ami
instance_type = var.app_instance_type
key_name = var.key_name

network_interfaces {
security_groups = [var.app_instance_sg_id]
}
}

resource "aws_autoscaling_group" "app" {
desired_capacity = 2
max_size = 4
min_size = 2
vpc_zone_identifier = var.private_subnet_ids

launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
}

4. Database Tier Module (modules/database)

main.tf

resource "aws_db_subnet_group" "db" {
name = "db-subnet-group"
subnet_ids = var.db_subnet_ids
}

resource "aws_db_instance" "main" {
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t2.micro"
db_name = var.db_name
username = var.db_user
password = var.db_password
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
db_subnet_group_name = aws_db_subnet_group.db.name
vpc_security_group_ids = [var.db_sg_id]
}

Deployment Steps

1️. Initialize Terraform

terraform init

2️. Plan the Deployment

terraform plan

3️. Apply the Configuration

terraform apply -auto-approve

4. Verify the Deployment

  • Check AWS Console for created resources.
  • Validate EC2 instances, ALB, and RDS.

5️.Destroy Infrastructure

terraform destroy -auto-approve

Key Components:

  • Web Tier: Public-facing ALB with Auto Scaling EC2 instances
  • App Tier: Internal ALB with private EC2 instances
  • Database Tier: MySQL RDS instance in isolated subnets
  • Network: VPC with public, private, and database subnets
  • Security: Security groups for each tier with least-privilege access

This architecture provides:

  • Horizontal scaling with Auto Scaling Groups
  • High availability across AZs
  • Network isolation between tiers
  • Secure communication between components
  • Managed database service with RDS

Conclusion:

This Terraform-driven 3-tier AWS deployment establishes a scalable, secure, and highly available architecture, leveraging infrastructure-as-code (IaC) best practices. By modularizing components (web, app, database) and isolating network layers, it ensures fault tolerance, simplified maintenance, and controlled access between tiers. The use of auto-scaling groups, ALBs, and RDS guarantees resilience and performance, while security groups enforce least-privilege principles. This foundation supports seamless scaling, cost optimization, and compliance readiness, providing a robust blueprint for modern cloud-native applications. Future enhancements like HTTPS, WAF, or secrets management can further strengthen production readiness.

………………………………………………………………………………………………………………………………………………………………………………………………

You’re welcome! Have a great time ahead! Enjoy your day!

Please Connect with me any doubts.

Mail: sriharimalapati6@gmail.com

LinkedIn: www.linkedin.com/in/

GitHub: https://github.com/Consultantsrihari

Medium: Sriharimalapati — Medium

Thanks for watching ##### %%%% Sri Hari %%%%

Comments

Popular posts from this blog

Cut AWS EC2 Costs with Python: Automate Cost Optimization Using AWS Lambda

Mastering AWS DevOps: Automation & Scripting with Python