Merge pull request #12 from elizabeth-ikechukwu/feat/week5-terraform

feat: week 5 & 6 — Terraform infrastructure and AWS EC2 deployment
This commit is contained in:
Elizabeth Nkwo Ikechukwu
2026-04-30 02:56:27 +01:00
committed by GitHub
18 changed files with 800 additions and 59 deletions
+73 -1
View File
@@ -241,4 +241,76 @@ jobs:
echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-backend:latest\`" >> $GITHUB_STEP_SUMMARY echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-backend:latest\`" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-backend:sha-${{ steps.vars.outputs.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-backend:sha-${{ steps.vars.outputs.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-frontend:latest\`" >> $GITHUB_STEP_SUMMARY echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-frontend:latest\`" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-frontend:sha-${{ steps.vars.outputs.sha }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`${{ env.DOCKERHUB_REGISTRY }}/task-frontend:sha-${{ steps.vars.outputs.sha }}\`" >> $GITHUB_STEP_SUMMARY
terraform-plan:
name: Terraform Plan
runs-on: ubuntu-latest
needs: build-and-integration-test
if: github.event_name == 'pull_request'
concurrency:
group: terraform
cancel-in-progress: false
defaults:
run:
working-directory: infra/terraform
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::424522917561:role/github-actions-scalable-task-platform
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.10.3
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -no-color -lock-timeout=5m
env:
TF_VAR_aws_account_id: ${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}
TF_VAR_aws_region: ${{ secrets.TF_VAR_AWS_REGION }}
terraform-apply:
name: Terraform Apply
runs-on: ubuntu-latest
needs: publish
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
concurrency:
group: terraform
cancel-in-progress: false
defaults:
run:
working-directory: infra/terraform
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::424522917561:role/github-actions-scalable-task-platform
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.10.3
- name: Terraform Init
run: terraform init
- name: Terraform Apply
run: terraform apply -auto-approve -lock-timeout=5m
env:
TF_VAR_aws_account_id: ${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}
TF_VAR_aws_region: ${{ secrets.TF_VAR_AWS_REGION }}
+8 -1
View File
@@ -138,4 +138,11 @@ dist
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
postman/ postman/
.postman .postman
# Terraform
.terraform/
.terraform.lock.hcl
terraform.tfvars
*.tfstate
*.tfstate.backup
tfplan
+26 -7
View File
@@ -12,7 +12,7 @@ Built in public, one week at a time.
- Containerization with Docker - Containerization with Docker
- CI/CD automation with GitHub Actions, Amazon ECR, and Docker Hub - CI/CD automation with GitHub Actions, Amazon ECR, and Docker Hub
- Infrastructure as Code with Terraform - Infrastructure as Code with Terraform
- Cloud deployment on AWS - Cloud deployment on AWS EC2
- Container orchestration with Kubernetes - Container orchestration with Kubernetes
- Monitoring and observability with Prometheus and Grafana - Monitoring and observability with Prometheus and Grafana
@@ -41,8 +41,8 @@ Built in public, one week at a time.
| 2 | Dockerize the backend | [Done](docs/week-2-update.md) | | 2 | Dockerize the backend | [Done](docs/week-2-update.md) |
| 3 | React frontend and Docker Compose | [Done](docs/week-3-update.md) | | 3 | React frontend and Docker Compose | [Done](docs/week-3-update.md) |
| 4 | CI/CD pipeline with GitHub Actions, ECR and Docker Hub | [Done](docs/week-4-update.md) | | 4 | CI/CD pipeline with GitHub Actions, ECR and Docker Hub | [Done](docs/week-4-update.md) |
| 5 | Terraform infrastructure setup | Upcoming | | 5 | Terraform infrastructure and CI/CD pipeline extension | [Done](docs/week-5-update.md) |
| 6 | Cloud deployment on AWS EC2 | Upcoming | | 6 | Cloud deployment on AWS EC2 | [Done](docs/week-6-update.md) |
| 7 | Database integration | Upcoming | | 7 | Database integration | Upcoming |
| 8 | Kubernetes deployment | Upcoming | | 8 | Kubernetes deployment | Upcoming |
| 9 | Monitoring with Prometheus and Grafana | Upcoming | | 9 | Monitoring with Prometheus and Grafana | Upcoming |
@@ -97,15 +97,34 @@ Frontend runs on http://localhost:80
## CI/CD Pipeline ## CI/CD Pipeline
Every pull request and push to `main` triggers a four-stage pipeline: Every pull request and push to `main` triggers a five-stage pipeline:
``` ```
lint -> unit-test -> build-and-integration-test -> publish lint -> unit-test -> build-and-integration-test -> publish -> terraform-apply
``` ```
The publish stage runs on merge to `main` only. Images are pushed to both Amazon ECR and Docker Hub, tagged with `latest` and the short Git SHA for traceability. - `terraform-plan` runs on every pull request — shows exactly what infrastructure will change before merging
- `terraform-apply` runs on merge to `main` — infrastructure is provisioned by the pipeline, never from a local machine
- The `publish` stage pushes images to both Amazon ECR and Docker Hub, tagged with `latest` and the short Git SHA
AWS authentication uses OIDC - no static credentials stored in GitHub secrets. AWS authentication uses OIDC — no static credentials stored in GitHub secrets.
---
## Infrastructure
All infrastructure is provisioned with Terraform and lives in `infra/terraform/`.
| Resource | Details |
|---|---|
| VPC | Custom VPC with public subnet |
| Networking | Internet gateway, route tables |
| Security | Security groups with least-privilege rules |
| Compute | EC2 t3.micro running Amazon Linux 2023 |
| IAM | Instance profile with SSM and ECR permissions |
| State | S3 remote backend with native state locking |
EC2 instances configure themselves on first boot via a user data script — Docker is installed, images are pulled from ECR, and containers start automatically. No SSH required. All access via AWS SSM Session Manager.
--- ---
+119
View File
@@ -0,0 +1,119 @@
# Week 5 — Terraform Infrastructure and CI/CD Pipeline Extension
## What I Built
Week 5 added two major pieces: full AWS infrastructure provisioned with Terraform, and an extended CI/CD pipeline that owns infrastructure changes end to end.
---
## Terraform Bootstrap
Before the main infrastructure, I created a bootstrap layer in `infra/terraform/bootstrap/` to set up remote state storage.
- S3 bucket with versioning, AES256 encryption, and public access blocked
- Native state locking using `use_lockfile = true` — available from Terraform 1.10 onwards
- No DynamoDB table required — Terraform writes a `.tflock` file directly to S3
This is the modern approach. One less AWS resource to manage, same production-grade protection.
---
## Main Infrastructure
All resources live in `infra/terraform/` and are split across focused files:
**`vpc.tf`**
- Custom VPC (`10.0.0.0/16`)
- Public subnet (`10.0.1.0/24`) in `us-east-1a`
- Internet gateway attached to the VPC
- Route table with a default route to the internet gateway
- Route table association to the public subnet
**`security-group.tf`**
- Security group allowing inbound HTTP (port 80) from anywhere
- All outbound traffic allowed
- Port 22 (SSH) intentionally closed — access via SSM only
**`iam.tf`**
- IAM role for EC2 with two managed policies attached:
- `AmazonSSMManagedInstanceCore` — enables SSM Session Manager access
- `AmazonEC2ContainerRegistryReadOnly` — allows pulling images from ECR
- Instance profile wrapping the role
**`ec2.tf`**
- Data source querying AWS at plan time for the latest Amazon Linux 2023 AMI — no hardcoded AMI ID
- EC2 `t3.micro` instance in the public subnet
- 20GB encrypted `gp3` root volume
- User data script that runs on first boot:
- Installs Docker and starts the service
- Authenticates to ECR
- Pulls backend and frontend images
- Starts both containers with `--restart unless-stopped`
**`outputs.tf`**
- Public IP of the EC2 instance — printed after every apply
---
## CI/CD Pipeline Extension
Added two new jobs to `.github/workflows/ci-cd.yml`:
**`terraform-plan`**
- Triggers on every pull request
- Runs `terraform init` and `terraform plan`
- Shows exactly what will change before any merge
- Depends on `build-and-integration-test` passing first
**`terraform-apply`**
- Triggers on merge to `main` only
- Runs `terraform init` and `terraform apply -auto-approve`
- Infrastructure is created by the pipeline — never from a local machine
Both jobs authenticate to AWS using the existing OIDC role. No static credentials.
---
## Production Discipline
The local machine provisions once to validate everything works. Then `terraform destroy` hands control to the pipeline permanently. From that point forward, infrastructure changes only happen through Git — a PR shows the plan, a merge triggers the apply.
---
## Architecture After Week 5
```
GitHub
│
├── Pull Request → terraform-plan (shows what will change)
│
└── Merge to main → lint → unit-test → build → publish → terraform-apply
│
AWS Infrastructure
VPC / Subnet / SG
IAM / EC2
```
---
## Files Added
```
infra/terraform/
├── vpc.tf
├── security-group.tf
├── iam.tf
├── ec2.tf
└── outputs.tf
.github/workflows/ci-cd.yml (terraform-plan and terraform-apply jobs added)
```
---
## Screenshots
See `docs/week-05/` for:
- Terraform plan output
- Terraform apply output showing 13 resources created
- GitHub Actions pipeline with all jobs passing
+110
View File
@@ -0,0 +1,110 @@
# Week 6 — Cloud Deployment on AWS EC2
## What I Built
Week 6 took everything built in Week 5 and made it live. TaskFlow — the full-stack Node.js and React application — is now running on AWS EC2, deployed automatically by the CI/CD pipeline with zero manual server setup.
---
## How Deployment Works
When a PR merges to `main`, the pipeline:
1. Runs lint, unit tests, and integration tests
2. Builds Docker images and pushes them to Amazon ECR
3. Runs `terraform apply` — creates or updates infrastructure
4. The EC2 instance boots and runs a user data script automatically
The user data script handles everything on first boot:
```
1. Update the system packages
2. Install Docker and start the service
3. Wait for Docker to be fully ready
4. Add ec2-user to the docker group
5. Install AWS CLI
6. Authenticate to ECR using the IAM instance profile
7. Pull task-backend:latest from ECR
8. Pull task-frontend:latest from ECR
9. Start backend container on port 3000 (restart unless stopped)
10. Wait for backend to be ready
11. Start frontend container on port 80 (restart unless stopped)
```
No SSH. No manual commands. The instance configures itself.
---
## Access Method
All server access is through **AWS SSM Session Manager** — not SSH.
- Port 22 is closed on the security group
- The EC2 IAM role has `AmazonSSMManagedInstanceCore` attached
- Sessions are opened via the AWS console or AWS CLI
This is the production-standard approach. No key pairs to manage, no inbound SSH exposure, full audit trail of every session in AWS CloudTrail.
---
## What Runs on EC2
| Container | Image Source | Port | Restart Policy |
|---|---|---|---|
| task-backend | Amazon ECR | 3000 (internal) | unless-stopped |
| task-frontend | Amazon ECR | 80 (public) | unless-stopped |
The frontend container runs Nginx, which serves the React build and proxies `/api` requests to the backend container.
---
## Architecture After Week 6
```
Internet
│
▼
EC2 Public IP :80
│
▼
task-frontend (Nginx)
│
├── Serves React frontend (/)
│
└── Proxies /api requests → task-backend :3000
│
▼
Express API
(in-memory store)
```
---
## Key Decisions
**Why no Docker Compose on EC2?**
The containers are run directly with `docker run` in the user data script. Docker Compose adds a dependency that needs to be installed separately on Amazon Linux 2023. Direct `docker run` with `--link` keeps the bootstrap simple and reliable.
**Why `--restart unless-stopped`?**
If the EC2 instance reboots, Docker restarts the containers automatically. The application comes back online without any intervention.
**Why ECR and not Docker Hub?**
ECR is private, in the same AWS region as the EC2 instance, and authenticated via IAM — no credentials to manage. Pulls are fast and free within the same region.
---
## Files Changed
```
infra/terraform/ec2.tf (user data script updated — sleep added for Docker readiness)
```
---
## Screenshots
See `docs/week-06/` for:
- TaskFlow running live in the browser at the EC2 public IP
- SSM Session Manager connection to the EC2 instance
- Docker containers running on the instance (`docker ps`)
- GitHub Actions pipeline showing full run with terraform-apply
-29
View File
@@ -1,29 +0,0 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRAuthentication",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "ECRImagePush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:CompleteLayerUpload",
"ecr:DescribeRepositories",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
],
"Resource": [
"arn:aws:ecr:us-east-1:424522917561:repository/task-backend",
"arn:aws:ecr:us-east-1:424522917561:repository/task-frontend"
]
}
]
}
-21
View File
@@ -1,21 +0,0 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GitHubActionsOIDC",
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::424522917561:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:elizabeth-ikechukwu/scalable-task-platform:*"
}
}
}
]
}
+61
View File
@@ -0,0 +1,61 @@
terraform {
required_version = ">= 1.10.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# ──────────────────────────────────────────────
# S3 Bucket — Terraform remote state storage
# Native state locking via use_lockfile = true
# (available from Terraform >= 1.10.0)
# No DynamoDB table required
# ──────────────────────────────────────────────
resource "aws_s3_bucket" "terraform_state" {
bucket = var.state_bucket_name
lifecycle {
prevent_destroy = true
}
tags = {
Name = var.state_bucket_name
Project = "scalable-task-platform"
ManagedBy = "terraform-bootstrap"
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
+9
View File
@@ -0,0 +1,9 @@
output "state_bucket_name" {
description = "S3 bucket name for Terraform remote state — copy this into your main backend config"
value = aws_s3_bucket.terraform_state.bucket
}
output "state_bucket_arn" {
description = "ARN of the Terraform state S3 bucket"
value = aws_s3_bucket.terraform_state.arn
}
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
variable "aws_region" {
description = "AWS region for all resources"
type = string
}
variable "state_bucket_name" {
description = "Name of the S3 bucket that stores Terraform state. Must be globally unique."
type = string
default = "scalable-task-platform-tfstate"
}
+95
View File
@@ -0,0 +1,95 @@
# ──────────────────────────────────────────────
# Data Source — Latest Amazon Linux 2023 AMI
# ──────────────────────────────────────────────
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# ──────────────────────────────────────────────
# EC2 Instance
# ──────────────────────────────────────────────
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.nginx.id]
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
user_data = <<-EOF
#!/bin/bash
set -e
# Update system
dnf update -y
# Install Docker
dnf install -y docker
systemctl enable docker
systemctl start docker
# Wait for Docker to be fully ready
sleep 10
# Add ec2-user to docker group
usermod -aG docker ec2-user
# Ensure SSM Agent is running (pre-installed on AL2023)
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
# Authenticate to ECR
aws ecr get-login-password --region ${var.aws_region} | \
docker login --username AWS --password-stdin \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com
# Pull images from ECR
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
docker pull ${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-frontend:latest
# Run backend
docker run -d \
--name task-backend \
--restart unless-stopped \
-p 3000:3000 \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-backend:latest
# Wait for backend to be ready
sleep 5
# Run frontend
docker run -d \
--name task-frontend \
--restart unless-stopped \
-p 80:80 \
--link task-backend:backend \
${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/task-frontend:latest
EOF
root_block_device {
volume_size = 20
volume_type = "gp3"
encrypted = true
delete_on_termination = true
}
metadata_options {
http_tokens = "required"
}
tags = {
Name = "${var.project_name}-app-server"
Project = var.project_name
Environment = var.environment
}
}
+52
View File
@@ -0,0 +1,52 @@
# ──────────────────────────────────────────────
# IAM Role — EC2 instance identity
# ──────────────────────────────────────────────
resource "aws_iam_role" "ec2_role" {
name = "${var.project_name}-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
tags = {
Name = "${var.project_name}-ec2-role"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Policy Attachments
# ──────────────────────────────────────────────
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role_policy_attachment" "ecr" {
role = aws_iam_role.ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}
# ──────────────────────────────────────────────
# Instance Profile
# ──────────────────────────────────────────────
resource "aws_iam_instance_profile" "ec2_profile" {
name = "${var.project_name}-ec2-profile"
role = aws_iam_role.ec2_role.name
tags = {
Name = "${var.project_name}-ec2-profile"
Project = var.project_name
Environment = var.environment
}
}
+22
View File
@@ -0,0 +1,22 @@
terraform {
required_version = ">= 1.10.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "scalable-task-platform-tfstate"
key = "main/terraform.tfstate"
region = "us-east-1"
use_lockfile = true
encrypt = true
}
}
provider "aws" {
region = var.aws_region
}
+29
View File
@@ -0,0 +1,29 @@
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_id" {
description = "ID of the public subnet"
value = aws_subnet.public.id
}
output "private_subnet_id" {
description = "ID of the private subnet"
value = aws_subnet.private.id
}
output "ec2_instance_id" {
description = "EC2 instance ID — use this to connect via SSM"
value = aws_instance.app.id
}
output "ec2_public_ip" {
description = "Public IP of the EC2 instance"
value = aws_instance.app.public_ip
}
output "ec2_public_dns" {
description = "Public DNS of the EC2 instance"
value = aws_instance.app.public_dns
}
+69
View File
@@ -0,0 +1,69 @@
# ──────────────────────────────────────────────
# Security Group — Nginx (Public)
# ──────────────────────────────────────────────
resource "aws_security_group" "nginx" {
name = "${var.project_name}-nginx-sg"
description = "Security group for Nginx reverse proxy"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from internet"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-nginx-sg"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Security Group — Backend (Private)
# ──────────────────────────────────────────────
resource "aws_security_group" "backend" {
name = "${var.project_name}-backend-sg"
description = "Security group for backend API - only accepts traffic from Nginx"
vpc_id = aws_vpc.main.id
ingress {
description = "Allow traffic from Nginx only"
from_port = 3000
to_port = 3000
protocol = "tcp"
security_groups = [aws_security_group.nginx.id]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-backend-sg"
Project = var.project_name
Environment = var.environment
}
}
+39
View File
@@ -0,0 +1,39 @@
variable "aws_region" {
description = "AWS region for all resources"
type = string
}
variable "aws_account_id" {
description = "AWS account ID — set in terraform.tfvars, never hardcoded"
type = string
}
variable "project_name" {
description = "Project name used for naming and tagging all resources"
type = string
default = "scalable-task-platform"
}
variable "environment" {
description = "Deployment environment"
type = string
default = "production"
}
variable "vpc_cidr" {
description = "CIDR block for the VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
description = "CIDR block for the public subnet"
type = string
default = "10.0.1.0/24"
}
variable "private_subnet_cidr" {
description = "CIDR block for the private subnet"
type = string
default = "10.0.2.0/24"
}
+78
View File
@@ -0,0 +1,78 @@
# ──────────────────────────────────────────────
# VPC
# ──────────────────────────────────────────────
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Subnets
# ──────────────────────────────────────────────
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidr
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet"
Project = var.project_name
Environment = var.environment
}
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidr
availability_zone = "${var.aws_region}a"
tags = {
Name = "${var.project_name}-private-subnet"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# Internet Gateway
# ──────────────────────────────────────────────
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
Project = var.project_name
Environment = var.environment
}
}
# ──────────────────────────────────────────────
# 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.main.id
}
tags = {
Name = "${var.project_name}-public-rt"
Project = var.project_name
Environment = var.environment
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}