Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,56 @@ It creates a Virtual Private Cloud (VPC) with public and private subnets, Intern
- Create environment-specific variable files (dev.tfvars or demo.tfvars) with the following structure:

```hcl
region = "us-east-1"
profile = "dev"
vpc_name = "dev"
vpc_cidr = "10.0.0.0/16"
region = "us-east-1"
profile = ""
vpc_name = ""
vpc_cidr = "10.0.0.0/16"
app_port = 8081
public_key_path = "~/.ssh/aws_key.pub"
name_prefix = "app-sg-dev"
ami_id = ""
subnet_tier = "public"
target_az = "us-east-1a"

tags = {
Project = ""
Owner = ""
Env = ""
}
```

---

## EC2 Instance

This Terraform setup also provisions an EC2 instance inside the created VPC.

EC2 Configuration Overview

- AMI: Ubuntu 20.04 LTS (or your custom AMI)
- Instance Type: t3.micro
- Root Volume: 25 GB GP2 (auto deleted on termination)
- SSH Key Pair: Generated from your local public key (aws_key_pair)
- Subnet Placement: Dynamically selected by tier (public/private) and Availability Zone
- Security Group:
- Ingress: TCP 22 (SSH), 80 (HTTP), 443 (HTTPS), and 8081 (your web app port)
- Egress: All outbound traffic allowed

### How It Works

- The public key defined in public_key_path will be uploaded to AWS as an EC2 Key Pair.
You can later connect using:

```shell
ssh -i ~/.ssh/aws_key.pem ubuntu@<EC2-Public-IP>
```

- The EC2 instance will be launched in the public subnet of the selected Availability Zone.
If you set subnet_tier = "private", it will launch in the private subnet instead (without public IP).
- The app_port (e.g. 8081) defines the custom application port opened in the security group.

---

## 🚀 How to Deploy (with Terraform Workspaces)

Workspaces let you maintain multiple, isolated sets of infrastructure (states)
Expand All @@ -68,7 +110,7 @@ terraform workspace list

You’ll see something like:

```
```txt
default
* dev
demo
Expand Down Expand Up @@ -124,6 +166,10 @@ After deployment, Terraform prints key identifiers:
- private_subnets
- igw_id
- route_tables
- chosen_subnet_id
- chosen_az
- application_sg_id
- instance_id

You can also view them via: `terraform output`

Expand Down
47 changes: 47 additions & 0 deletions ec2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
locals {
az_to_public_subnet_id = {
for az, s in aws_subnet.public :
az => s.id
}

az_to_private_subnet_id = {
for az, s in aws_subnet.private :
az => s.id
}

default_az = sort(keys(local.az_to_public_subnet_id))[0]
chosen_az = coalesce(var.target_az, local.default_az)

chosen_subnet_id = (var.subnet_tier == "public"
? lookup(local.az_to_public_subnet_id, local.chosen_az, null)
: lookup(local.az_to_private_subnet_id, local.chosen_az, null)
)
}

resource "aws_instance" "app" {
ami = var.ami_id
instance_type = var.instance_type
subnet_id = local.chosen_subnet_id
vpc_security_group_ids = [aws_security_group.app_sg.id]

# assign ssh key
key_name = var.key_name != "" ? var.key_name : null

# Do NOT protect from accidental termination
disable_api_termination = false

# Root volume requirements
root_block_device {
volume_type = "gp2"
volume_size = 25
delete_on_termination = true
}

# ensure a public IP if your subnet doesn't auto-assign
associate_public_ip_address = var.subnet_tier == "public" ? true : false

tags = {
Name = "${var.name_prefix}-ec2"
Role = "webapp"
}
}
6 changes: 6 additions & 0 deletions keypair.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "aws_key_pair" "app" {
key_name = var.key_name
public_key = file(var.public_key_path)

tags = { Name = "aws_key" }
}
16 changes: 16 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,19 @@ output "route_tables" {
private = aws_route_table.private.id
}
}

output "chosen_subnet_id" {
value = local.chosen_subnet_id
}

output "chosen_az" {
value = local.chosen_az
}

output "application_sg_id" {
value = aws_security_group.app_sg.id
}

output "instance_id" {
value = aws_instance.app.public_ip
}
44 changes: 44 additions & 0 deletions security-group.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
resource "aws_security_group" "app_sg" {
name = "${var.name_prefix}-sg"
description = "Web App SG: 22,80,443,app open to world"
vpc_id = aws_vpc.csye6225.id

tags = { Name = "${var.name_prefix}-sg" }
}

locals {
app_ingress_ports = ["22", 80, 443, var.app_port]
}

resource "aws_vpc_security_group_ingress_rule" "ipv4" {
for_each = toset([for p in local.app_ingress_ports : tostring(p)])
security_group_id = aws_security_group.app_sg.id
cidr_ipv4 = "0.0.0.0/0"
from_port = tonumber(each.value)
to_port = tonumber(each.value)
ip_protocol = "tcp"
description = "Allow TCP ${each.value} from anywhere (IPv4)"
}

resource "aws_vpc_security_group_ingress_rule" "ipv6" {
for_each = var.enable_ipv6 ? toset([for p in local.app_ingress_ports : tostring(p)]) : toset([])
security_group_id = aws_security_group.app_sg.id
cidr_ipv6 = "::/0"
from_port = tonumber(each.value)
to_port = tonumber(each.value)
ip_protocol = "tcp"
description = "Allow TCP ${each.value} from anywhere (IPv6)"
}

# Egress (allow all traffic)
resource "aws_vpc_security_group_egress_rule" "all_out_ipv4" {
security_group_id = aws_security_group.app_sg.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
}
resource "aws_vpc_security_group_egress_rule" "all_out_ipv6" {
count = var.enable_ipv6 ? 1 : 0
security_group_id = aws_security_group.app_sg.id
cidr_ipv6 = "::/0"
ip_protocol = "-1"
}
56 changes: 56 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,62 @@ variable "vpc_cidr" {
type = string
}

variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}

variable "ami_id" {
description = "Your custom AMI ID for the instance"
type = string
}

variable "subnet_tier" {
description = "Which tier to place EC2 in: public or private"
type = string
default = "public"
validation {
condition = contains(["public", "private"], var.subnet_tier)
error_message = "subnet_tier must be 'public' or 'private'."
}
}

variable "target_az" {
description = "AZ for the EC2 (e.g., us-east-1a). If null, pick the first AZ you created."
type = string
default = null
}

variable "key_name" {
description = "Existing EC2 key pair name (for SSH); leave empty to skip"
type = string
default = ""
}

variable "app_port" {
description = "Port your application listens on"
type = number
default = 8081
}

variable "enable_ipv6" {
type = bool
default = true
}

variable "public_key_path" {
description = "Path to your SSH public key (.pub)"
type = string
default = "~/.ssh/aws_key.pub"
}

variable "name_prefix" {
description = "Name prefix for resources"
type = string
default = "app"
}

variable "tags" {
description = "Common tags applied to all resources"
type = map(string)
Expand Down