Multi-Tier Web Application#
This example deploys a 3-tier web application architecture on AWS using Auto Scaling Groups and an Application Load Balancer.
Architecture#
Internet → ALB → ASG (Web/App instances in public subnets)
→ RDS (PostgreSQL in private subnet)Files#
| File | Description |
|---|---|
main.tf |
Full 3-tier setup: VPC, subnets, ALB, ASG, RDS, security groups |
user_data.sh |
Bootstrap script for EC2 instances (Apache + PHP app) |
Code#
main.tf#
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
provider "aws" {
region = var.region
}
# Variables
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
# Data Sources
data "aws_availability_zones" "available" {
state = "available"
}
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "${var.environment}-igw" }
}
# Public Subnets
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-${count.index + 1}"
Environment = var.environment
Tier = "public"
}
}
# Private Subnets
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 2)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.environment}-private-${count.index + 1}"
Environment = var.environment
Tier = "private"
}
}
# Security Groups
resource "aws_security_group" "alb" {
name = "${var.environment}-alb-sg"
description = "ALB security group"
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"]
}
}
resource "aws_security_group" "web" {
name = "${var.environment}-web-sg"
description = "Web server security group"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "rds" {
name = "${var.environment}-rds-sg"
description = "RDS security group"
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
}
# Random password
resource "random_password" "db" {
length = 24
special = false
}
# Application Load Balancer
resource "aws_lb" "main" {
name = "${var.environment}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
tags = {
Name = "${var.environment}-alb"
Environment = var.environment
}
}
resource "aws_lb_target_group" "web" {
name = "${var.environment}-web-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 5
interval = 30
path = "/"
timeout = 5
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
# Launch Template
resource "aws_launch_template" "web" {
name_prefix = "${var.environment}-web-"
image_id = data.aws_ami.amazon_linux_2.id
instance_type = "t2.micro"
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
environment = var.environment
}))
vpc_security_group_ids = [aws_security_group.web.id]
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.environment}-web-asg"
Environment = var.environment
}
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "web" {
name = "${var.environment}-web-asg"
desired_capacity = 2
min_size = 1
max_size = 4
vpc_zone_identifier = aws_subnet.private[*].id
target_group_arns = [aws_lb_target_group.web.arn]
launch_template {
id = aws_launch_template.web.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.environment}-web-asg"
propagate_at_launch = true
}
}
# RDS Instance
resource "aws_db_subnet_group" "main" {
name = "${var.environment}-db-subnet-group"
subnet_ids = aws_subnet.private[*].id
}
resource "aws_db_instance" "main" {
identifier = "${var.environment}-db"
engine = "postgres"
engine_version = "15.3"
instance_class = "db.t3.micro"
allocated_storage = 20
db_name = "appdb"
username = "admin"
password = random_password.db.result
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
skip_final_snapshot = var.environment != "prod"
tags = {
Name = "${var.environment}-db"
Environment = var.environment
}
}
# Outputs
output "alb_dns_name" {
description = "ALB DNS name"
value = aws_lb.main.dns_name
}
output "db_endpoint" {
description = "RDS endpoint"
value = aws_db_instance.main.endpoint
}
output "db_password" {
description = "Database password"
value = random_password.db.result
sensitive = true
}user_data.sh#
#!/bin/bash
# User data script for multi-tier web example
echo "Starting web server for ${environment} environment..."
yum update -y
yum install -y httpd
systemctl enable httpd
systemctl start httpd
echo "<h1>${environment} Web Server</h1>" > /var/www/html/index.html
echo "<p>Host: $(hostname)</p>" >> /var/www/html/index.html
echo "<p>Environment: ${environment}</p>" >> /var/www/html/index.htmlUsage#
terraform init
terraform plan
terraform applyResources Created#
- VPC with public/private subnets across 2 AZs
- Application Load Balancer (internet-facing)
- Auto Scaling Group with launch template
- RDS PostgreSQL in private subnets
- Security Groups with least privilege