Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

19 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”— Secure Multi-Tier AWS Application Platform with Private Database Connectivity

Production-inspired AWS networking project with VPC peering, ALB, EC2, ECS on EC2, Flask, Docker, Terraform, and RDS MySQL




AWS Terraform Amazon VPC VPC Peering Application Load Balancer EC2 ECS RDS Python Docker SSM Security Groups Infrastructure as Code Status


πŸ“Œ Overview

This project builds a multi-VPC AWS application architecture where application workloads in separate VPCs connect privately to a centralized RDS MySQL database through VPC peering.

The project includes two compute patterns:

  1. ECS on EC2 running a Dockerized Flask application.
  2. Standalone EC2 running a Flask application through user data and systemd.

Both application paths are placed behind Application Load Balancers and connect to a private MySQL database in a separate VPC.

The goal of this project is to demonstrate real AWS networking, private database connectivity, security group design, ALB troubleshooting, ECS on EC2 behavior, user-data bootstrapping, and Infrastructure as Code with Terraform.


🧠 Problem Statement

Many cloud environments separate applications and databases into different networks for security, isolation, and operational control.

However, this creates real engineering challenges:

  • How do applications in one VPC privately reach a database in another VPC?
  • How do security groups behave across VPC peering?
  • How do ALBs forward traffic to private compute?
  • How do ECS tasks differ from EC2 instances when connecting to databases?
  • How do you troubleshoot timeouts, failed health checks, and container startup issues?
  • How do you build this repeatably with Terraform?

This project answers those questions through a working AWS implementation.


🎯 Project Objective

Build a production-inspired AWS lab where:

  • public users access applications through ALBs
  • application workloads run privately
  • database access stays private
  • RDS is isolated in a separate VPC
  • VPC peering enables private cross-VPC communication
  • security groups control application-to-database access
  • ECS and EC2 compute patterns are both tested
  • Terraform provisions the environment repeatably

πŸ—οΈ Architecture Diagram

Save your architecture image as:

Evidence/01-architecture/architecture-diagram.png

Architecture Diagram


πŸ”„ Architecture Flow

This project has two application paths that both reach the same private RDS MySQL database through VPC peering.

                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚          Users           β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                       β”‚
                                       β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚ Application Load Balancer β”‚
                         β”‚        Public Subnet      β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                       β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚                                     β”‚
                    β–Ό                                     β–Ό
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚ ECS on EC2 Flask App         β”‚       β”‚ Standalone EC2 Flask App     β”‚
      β”‚ Private Subnet               β”‚       β”‚ Private Subnet               β”‚
      β”‚ Dockerized App Runtime       β”‚       β”‚ User Data + systemd Runtime  β”‚
      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚                                     β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β”‚
                                        β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚      VPC Peering          β”‚
                         β”‚  Private Cross-VPC Route  β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                       β”‚
                                       β–Ό
                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚ Private RDS MySQL Databaseβ”‚
                         β”‚       Database VPC        β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧰 Technology Stack

Category Technologies
Cloud Provider AWS
Infrastructure as Code Terraform
Networking Amazon VPC, Public/Private Subnets, Route Tables, Internet Gateway, NAT Gateway, VPC Peering
Load Balancing Application Load Balancer, Target Groups, Health Checks
Compute Pattern 1 Amazon ECS on EC2, Auto Scaling Group, Capacity Provider
Compute Pattern 2 Amazon EC2, User Data, systemd, Gunicorn
Container Registry Amazon ECR
Application Runtime Python, Flask, Gunicorn
Containerization Docker
Database Amazon RDS for MySQL
Private Access AWS Systems Manager Session Manager
Security IAM Roles, Security Groups, Private Subnets, Least-Privilege Network Access
Logging & Troubleshooting CloudWatch Logs, systemd Journals, Docker Logs, ALB Target Health

Demo

EC2

ec2-demo1.mp4

ECS

ecs-demo.1.mp4

✨ What This Project Demonstrates

🌐 Multi-VPC Networking

Designing and deploying multiple AWS VPCs with isolated application and database layers.

πŸ”— VPC Peering

Enabling private communication between separate VPCs without exposing internal traffic to the public internet.

πŸ—„οΈ Cross-VPC App-to-DB Connectivity

Connecting application workloads in one VPC to an RDS MySQL database in another VPC.

βš–οΈ Application Load Balancing

Routing public HTTP traffic through ALBs to private compute resources securely and reliably.

πŸ“¦ ECS on EC2

Running containerized Flask applications on Amazon ECS using EC2-backed capacity.

πŸ–₯️ EC2 Bootstrapping

Using EC2 user data and systemd to automatically configure and launch an application at boot time.

🐳 Docker + ECR Workflow

Building a Docker image locally, tagging it correctly, and pushing it to Amazon ECR for deployment.

πŸ›’οΈ Private RDS Access

Keeping the database private while still allowing approved application traffic over internal AWS networking.

πŸ›‘οΈ Security Group Design

Using source-based security group rules to tightly control traffic between ALBs, app tiers, and the database.

🧱 Terraform Dependency Management

Handling resource ordering and cross-resource dependencies, especially around peering and security rules.

🩺 ALB Health Check Troubleshooting

Diagnosing failed health checks, target group issues, and port mismatches that caused 502 errors.

🧰 Real Infrastructure Debugging

Troubleshooting real issues across containers, ports, database access, networking, and runtime behavior.

πŸ” SSM-Based Private Access

Accessing private EC2 instances securely through AWS Systems Manager instead of exposing SSH publicly.

πŸ“Š End-to-End Validation

Verifying the stack through /health, /info, and /dbcheck application routes.

πŸ—οΈ Production-Inspired Design

Combining networking, security, compute, and database patterns that reflect real-world cloud environments.

πŸš€ Practical Cloud Engineering

Showing not just deployment, but the ability to design, build, test, and fix a complete AWS solution.

βœ… What This Builds

🌐 Networking

  • Separate VPCs for the application and database layers
  • Public and private subnets for tiered network segmentation
  • VPC peering routes for private app-to-database communication

βš–οΈ Load Balancing

  • Application Load Balancers for routing external traffic to application workloads

πŸ“¦ Containerized Application

  • ECS cluster using EC2-backed capacity
  • ECS capacity provider backed by an Auto Scaling Group
  • ECS task definition and ECS service for the containerized Flask application
  • Dockerized Flask application image pushed to Amazon ECR

πŸ–₯️ Standalone Application

  • Standalone EC2 Flask application deployed through user data

πŸ—„οΈ Database

  • Private RDS MySQL database

πŸ” Security

  • Security groups for the ALB, app workloads, ECS tasks, EC2 instances, and database access

πŸ› οΈ Operations & Validation

  • SSM access for private instance troubleshooting without public SSH
  • Health check and database validation endpoints

🧱 Core AWS Resources

AWS Resource Purpose
Amazon VPC Creates isolated network boundaries for application and database environments.
Public Subnets Place internet-facing ALBs where users can reach them.
Private Subnets Host ECS, EC2, and RDS resources without direct public exposure.
VPC Peering Enables private cross-VPC communication between application and database networks.
Application Load Balancer Routes public HTTP traffic to healthy private application targets.
Amazon EC2 Runs the standalone Flask app and ECS container instances.
Amazon ECS Orchestrates the Dockerized Flask app on EC2-backed capacity.
Amazon ECR Stores and serves the Docker image used by ECS.
Amazon RDS for MySQL Provides the private managed MySQL database backend.
IAM Grants EC2, ECS, ECR, SSM, and logging permissions.
Security Groups Restrict traffic between ALB, app workloads, and database.
Systems Manager Enables private instance access without SSH exposure.
CloudWatch Logs Captures application, ECS, Docker, and system logs for troubleshooting.

πŸ“‚ Project Structure

PROJECT 1/
β”œβ”€β”€ app/                          # Flask application source code
β”‚   β”œβ”€β”€ app.py                    # Flask API with health and DB validation endpoints
β”‚   β”œβ”€β”€ Dockerfile                # Container image definition
β”‚   └── requirements.txt          # Python dependencies
β”‚
β”œβ”€β”€ Evidence/                     # Project screenshots and validation proof
β”‚   β”œβ”€β”€ Evidence.md               # Deployment notes and testing evidence
β”‚   └── architecture-diagram.png  # Architecture diagram
β”‚
β”œβ”€β”€ modules/                      # Reusable Terraform modules
β”‚   β”œβ”€β”€ subnet/
β”‚   β”‚   β”œβ”€β”€ main.tf               # Subnet resources
β”‚   β”‚   β”œβ”€β”€ output.tf             # Subnet outputs
β”‚   β”‚   └── variable.tf           # Subnet variables
β”‚   β”‚
β”‚   └── vpc/
β”‚       β”œβ”€β”€ main.tf               # VPC resources
β”‚       β”œβ”€β”€ output.tf             # VPC outputs
β”‚       └── variable.tf           # VPC variables
β”‚
β”œβ”€β”€ scripts/                      # Build and deployment automation
β”‚   β”œβ”€β”€ build_and_push_ecr.sh     # Builds Docker image and pushes it to ECR
β”‚   β”œβ”€β”€ build_everything.sh       # Runs the full build/deploy workflow
β”‚   └── destroy_everything.sh     # Destroys deployed infrastructure
β”‚
β”œβ”€β”€ templates/                    # EC2 and ECS bootstrap templates
β”‚   β”œβ”€β”€ ec2_app_user_data.sh.tftpl # User data for standalone EC2 Flask app
β”‚   └── ecs_user_data.sh.tftpl     # User data for ECS container instances
β”‚
β”œβ”€β”€ vpc-peering.tf                # Core networking resources
β”œβ”€β”€ vpc-1.tf                      # Primary region infrastructure
β”œβ”€β”€ vpc-2.tf                      # Secondary region / additional infrastructure
β”œβ”€β”€ vpc-3.tf                      # Tertiary region / additional infrastructure
β”œβ”€β”€ workflow.tf                   # Deployment workflow resources
β”œβ”€β”€ variable.tf                   # Terraform input variables
β”œβ”€β”€ output.tf                     # Terraform outputs
β”œβ”€β”€ .terraform.lock.hcl           # Terraform provider lock file
└── terraform.tfstate             # Local Terraform state file

πŸ–₯️ Application Routes

The application exposes a small set of routes that help validate each layer of the deployment: the web app, the load balancer, the runtime environment, and the database connection.

Route Method What It Tests Description
/ GET Frontend + app runtime Loads the visual dashboard page for the ECS or EC2 version of the app.
/health GET ALB health check + app availability Returns a lightweight health response used to confirm the app is reachable.
/info GET Runtime metadata Shows app mode, hostname, database host, database name, and database user.
/dbcheck GET App-to-database path Opens a live MySQL connection and confirms the app can reach the private RDS database.
/          β†’ Visual dashboard
/health    β†’ Application health
/info      β†’ Runtime metadata
/dbcheck   β†’ RDS connectivity validation

πŸ§ͺ Expected App Behavior

The Flask application exposes several validation routes to confirm that the workload is running correctly and can connect to the private RDS MySQL database.


βœ… Health Check

GET /health

Expected response:

{
  "status": "ok",
  "app_mode": "EC2 or ECS",
  "hostname": "instance-or-container-hostname"
}

This confirms that the Flask application is running successfully.

πŸ—„οΈ Database Connectivity Check

GET /dbcheck

Expected response:

{
  "status": "connected",
  "result": {
    "current_db": "labdb",
    "db_time": "timestamp"
  }
}

This confirms that the application can privately connect to the RDS MySQL database.

A visual dashboard that displays:

  • Application Mode β€” identifies whether the app is running on EC2 or ECS
  • Hostname β€” shows the instance or container hostname
  • Database Host β€” displays the configured RDS database endpoint
  • Database Name β€” shows the active MySQL database name
  • Database User β€” shows the database user configured for the application
  • Available Routes β€” lists the supported Flask endpoints
  • Test Buttons β€” provides quick validation buttons for /health, /info, and /dbcheck

πŸš€ Quick Start

1️⃣ Manual Terraform Deployment

Clone the repository

git clone <your-repository-url>
cd <your-project-folder>

Review Terraform variables

Before deploying, update:

variable.tf

Important variables:

project_name
aws_region
instance_type
container_port
app_port
db_name
db_username
db_password
image_tag

Initialize Terraform

terraform init

Validate configuration

terraform validate

Review execution plan

terraform plan

Deploy base infrastructure

terraform apply -auto-approve

Build and push Docker image to ECR

The ECS service depends on a Docker image being available in Amazon ECR.

cd scripts
chmod +x build_and_push_ecr.sh
./build_and_push_ecr.sh

The script should:

1. Start Docker if needed
2. Build the local Docker image
3. Authenticate to Amazon ECR
4. Tag the image with the ECR repository URI
5. Push the image to Amazon ECR

Validate the image exists in ECR

aws ecr describe-images \
  --repository-name Secure-Multi-Tier-AWS-Application-Platform-with-Private-Database-Connectivity \
  --region us-east-1 \
  --query "imageDetails[].imageTags"

Re-apply Terraform to start ECS with the pushed image

After the image exists in ECR, return to the project root and re-apply Terraform so the ECS service can launch using the uploaded image.

cd ..
terraform init
terraform apply -auto-approve

2️⃣ Automated Deployment

Clone the repository

git clone <your-repository-url>
cd <your-project-folder>

Review Terraform variables

Before running the automated script, update:

variable.tf

Confirm these values are correct:

project_name
aws_region
instance_type
container_port
app_port
db_name
db_username
db_password
image_tag

Run the automation script

The build_everything.sh script performs the full deployment workflow.

cd scripts
chmod +x build_everything.sh
./build_everything.sh

What the script does

1. Initializes Terraform
2. Applies the base AWS infrastructure
3. Creates the ECR repository
4. Builds the Docker image locally
5. Authenticates to Amazon ECR
6. Tags and pushes the image to ECR
7. Re-applies Terraform
8. Starts or refreshes the ECS service

Validate the deployment

curl http://<alb-dns-name>/health
curl http://<alb-dns-name>/dbcheck

Expected result

{
  "status": "connected"
}

Use this method when you want the fastest path to deploy the infrastructure, upload the container image, and trigger ECS to run with the latest ECR image.

βš™οΈ Manual vs Automated Deployment

This project supports two deployment paths: a manual Terraform workflow and an automated script-based workflow. Both reach the same final architecture, but the automated path reduces repeated commands, lowers the chance of human error, and speeds up the build-test-deploy cycle.

Deployment Method What You Do Manually Estimated Time Best Use Case
Manual Deployment Run Terraform commands, build the Docker image, push to ECR, validate the image, then re-apply Terraform to start ECS. 15–30 minutes
Depends on Docker startup, Terraform apply time, image upload speed, and troubleshooting.
Best for learning, debugging, and understanding every step in the deployment workflow.
Automated Deployment Run one script that handles Terraform initialization, infrastructure deployment, Docker build, ECR push, and Terraform re-apply. 5–10 minutes
Most of the workflow runs without repeated manual commands.
Best for repeatable deployments, faster testing, and reducing manual mistakes.

⏱️ Time Savings Estimate

Workflow Step Manual Deployment Automated Deployment Automation Benefit
Terraform initialization Manual command Handled by script Removes repeated setup steps
Base infrastructure deployment Manual terraform apply Handled by script Creates required AWS resources consistently
Docker image build Manual script execution Included in automation Reduces context switching
ECR authentication and image push Manual validation required Handled by script Prevents missing image/tag issues
Second Terraform apply Must be remembered manually Handled automatically Ensures ECS can start with the uploaded image
Total estimated time 15–30 minutes 5–10 minutes Saves roughly 50–70% of deployment time

πŸš€ Why Automation Matters

⚑ Faster Deployments

Automation reduces the number of manual commands required to build, push, and deploy the application.

🧱 Repeatable Builds

The same script can be run multiple times to recreate the workflow with consistent results.

πŸ›‘οΈ Fewer Human Errors

Automation helps avoid missed steps such as forgetting to push the ECR image or re-apply Terraform.

πŸ” Better Iteration

Faster deployment cycles make it easier to test infrastructure, application changes, and networking updates.

πŸ“¦ Image-to-ECS Flow

The script connects the Docker build, ECR push, and ECS deployment flow into one repeatable process.

πŸ—οΈ Production-Like Workflow

Real environments rely on automated deployment pipelines instead of repeated manual console or CLI steps.

🧠 Key Takeaway

Manual deployment is useful for learning how each piece works. Automated deployment is better for speed, consistency, and repeatability.

Manual Deployment
  β†’ Best for learning and troubleshooting

Automated Deployment
  β†’ Best for repeatable infrastructure delivery

πŸ” ECS Deployment Flow Docker image built locally ↓ Image pushed to ECR ↓ ECS task definition references ECR image ↓ ECS service launches task ↓ ECS task receives its own ENI and security group ↓ ALB forwards traffic to task ↓ Task connects to RDS over VPC peering

πŸ–₯️ EC2 Deployment Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  🌐 1. User Request                           β”‚
β”‚  User sends traffic to the public endpoint   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  βš–οΈ 2. Application Load Balancer              β”‚
β”‚  ALB receives traffic and routes requests    β”‚
β”‚  to healthy private EC2 targets              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ–₯️ 3. Private EC2 Instance                   β”‚
β”‚  EC2 launches inside a private subnet        β”‚
β”‚  with no direct public SSH access            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  βš™οΈ 4. User Data Bootstrap                    β”‚
β”‚  Installs Python, Flask, PyMySQL,            β”‚
β”‚  Gunicorn, and required system packages      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ“¦ 5. Flask App Setup                        β”‚
β”‚  Application code is written to disk         β”‚
β”‚  during instance initialization              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸš€ 6. systemd + Gunicorn                     β”‚
β”‚  systemd starts and manages the Flask app    β”‚
β”‚  as a persistent Gunicorn service            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ” 7. Private Database Access                β”‚
β”‚  EC2 Flask app connects to RDS MySQL         β”‚
β”‚  privately over VPC peering                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ” Security Architecture

🌐 Public Access
Internet β†’ ALB:80
Only the Application Load Balancers are internet-facing.

        ↓

🧱 Private Application Access
ALB SG β†’ App SG
Application workloads run in private subnets and only accept traffic from the ALB security group.

        ↓

πŸ—„οΈ Private Database Access
App SG / ECS Task SG β†’ DB SG:3306
The database security group allows MySQL traffic only from approved application security groups.

        ↓

πŸ› οΈ Administrative Access
IAM Role + SSM β†’ Private EC2 Access
Private EC2 instances are accessed through AWS Systems Manager Session Manager.

No public SSH access is required.

πŸ›‘οΈ Security Group Design

Security Group Inbound Source Port Purpose
ALB SG Internet 0.0.0.0/0 80 Allows public HTTP traffic to reach the Application Load Balancer
EC2 App SG ALB SG App Port Allows the ALB to forward traffic to the standalone EC2 Flask app
ECS Task SG ALB SG App Port Allows the ALB to forward traffic to ECS Flask tasks
DB SG EC2 App SG 3306 Allows the EC2 Flask app to connect to RDS MySQL
DB SG ECS Task SG 3306 Allows ECS tasks to connect to RDS MySQL
ECS Instance SG No public inbound required N/A Supports ECS host outbound access and SSM-based administration

The database does not allow public access. MySQL traffic is only accepted from approved application security groups.

🧭 VPC Peering Design

This project uses VPC peering to enable private communication between the application VPCs and the database VPC.

The peering connection allows application workloads to reach the private RDS MySQL database without sending database traffic over the public internet.


πŸ” Required Route Table Entries

Each side of the peering connection needs a route table entry.

Route Table Destination Target
Application VPC Route Table Database VPC CIDR VPC Peering Connection
Database VPC Route Table Application VPC CIDR VPC Peering Connection

🧱 Private Traffic Flow

Application VPC
   ↓
VPC Peering Connection
   ↓
Database VPC
   ↓
Private RDS MySQL

⚠️ Terraform Dependency Note

When security group rules reference security groups across VPC peering, Terraform may try to create those rules before the peering connection and route table entries are fully ready.

To avoid dependency issues, add explicit depends_on relationships where needed.

VPC Peering Connection
   ↓
Route Table Entries
   ↓
Cross-VPC Security Group Rules

This ensures that private routing exists before Terraform applies the database access rules.

This prevents race-condition style failures where the security group rule appears correct but connectivity does not work reliably.

πŸ§ͺ Validation and Testing

Use the following commands to validate that the ALB, Flask application, and private RDS connectivity are working correctly.


βœ… Test ALB Health Route

curl http://<alb-dns-name>/health

Expected result:

{
  "status": "ok",
  "app_mode": "EC2 or ECS",
  "hostname": "instance-or-container-hostname"
}

πŸ–₯️ Test Application Dashboard

curl http://<alb-dns-name>/

Expected result:

The root route should return the application dashboard showing runtime details, database configuration values, and available test routes.


πŸ—„οΈ Test Database Connectivity

curl http://<alb-dns-name>/dbcheck

Expected result:

{
  "status": "connected"
}

This confirms that the application can reach the private RDS MySQL database through the private network path.


πŸ”Ž Debugging from SSM

Private EC2 instances are accessed through AWS Systems Manager Session Manager, not public SSH.

After connecting to the private EC2 instance through Session Manager, use the following commands to troubleshoot the application.


βœ… Check Flask Service Status

sudo systemctl status flask-db-app --no-pager -l

πŸ“œ Check Flask Application Logs

sudo journalctl -u flask-db-app --no-pager -n 100

🌐 Check Listening Ports

sudo ss -lntp | grep -E ':80|:8080|:5000'

πŸ§ͺ Test Local Health Route

curl -i http://127.0.0.1:8080/health

πŸ—„οΈ Test Database Socket Connectivity

python - <<'PY'
import os
import socket

host = os.getenv("DB_HOST")
port = int(os.getenv("DB_PORT", "3306"))

s = socket.create_connection((host, port), timeout=5)
print("TCP connection to database succeeded")
s.close()
PY

This test confirms that the private EC2 instance can establish a TCP connection to the RDS database endpoint.


πŸ§ͺ Debugging ECS Containers

Use these commands from the ECS container instance to inspect running containers and test database connectivity from inside the application container.


πŸ“¦ List Running Docker Containers

sudo docker ps

🐳 Enter the Application Container

sudo docker exec -it <container-id> sh

πŸ” Check Database Environment Variables

env | grep DB

πŸ—„οΈ Test Database Connectivity from Inside the Container

python - <<'PY'
import os
import socket

host = os.getenv("DB_HOST")
port = int(os.getenv("DB_PORT", "3306"))

s = socket.create_connection((host, port), timeout=5)
print("TCP connection to database succeeded")
s.close()
PY

This confirms that the ECS task can reach the private RDS MySQL database through VPC peering and the configured security group rules.


🧩 Engineering Challenges and Fixes

Problem Root Cause Fix
ECS EC2 instance did not appear in the cluster ECS agent startup ordering issue Deferred ECS startup until after cloud-init completed
ECS task failed to pull image ECR image tag mismatch Pushed the correct image tag to ECR and aligned it with the task definition
Docker push failed Missing AWS_REGION variable Defined AWS_REGION in the build script
RDS connection timed out Database security group did not allow the actual source security group Allowed inbound MySQL traffic from the ECS task SG and EC2 app SG
Security group-only rule behaved inconsistently Terraform dependency order issue with VPC peering Added explicit depends_on for peering and route resources
ALB returned 502 Bad Gateway Target was unhealthy due to incorrect app port or failed health check Aligned the target group port, application port, health check path, and SG rules
MySQL query failed SQL alias used a reserved-keyword-style name Changed the alias to db_time
Terraform ECS service recreation failed Old ECS service was still draining Waited for the service to become inactive or temporarily used a new service name

πŸ—οΈ Architecture Decisions

Decision Reason
Use ALB as the public entry point Keeps compute resources private while still allowing controlled HTTP access
Use private subnets for compute Reduces direct exposure of EC2 and ECS workloads
Place RDS in a separate VPC Demonstrates database isolation and cross-VPC application access
Use VPC peering Enables private IP communication without exposing the database publicly
Use security-group-to-security-group database rules Provides tighter access control than broad CIDR-based rules
Use SSM instead of SSH Avoids public SSH exposure and removes the need to manage SSH keys
Use Terraform Makes the infrastructure repeatable, reviewable, and easier to rebuild
Use /dbcheck route Provides simple application-level validation of database connectivity

πŸ“ˆ Production Roadmap

This project is production-inspired but still a lab. The next improvements would make the platform more secure, reliable, automated, and observable.


Phase 1 β€” Security Hardening

  • Move database credentials to AWS Secrets Manager
  • Add HTTPS using AWS Certificate Manager
  • Redirect HTTP traffic to HTTPS
  • Add AWS WAF in front of the Application Load Balancers
  • Replace broad IAM permissions with tighter custom IAM policies
  • Enable or verify RDS encryption at rest
  • Add stricter outbound rules where practical
  • Add least-privilege IAM policies for EC2, ECS, ECR, SSM, and logging

Phase 2 β€” Reliability

  • Enable Multi-AZ RDS
  • Add Auto Scaling Groups for the EC2 application tier
  • Run multiple ECS container instances across Availability Zones
  • Enable ALB access logs
  • Add CloudWatch alarms for ALB, ECS, EC2, and RDS
  • Improve container-level logging
  • Add health check tuning for more graceful deployments

Phase 3 β€” CI/CD

  • Add a GitHub Actions or Jenkins pipeline
  • Automate Docker image build and ECR push
  • Run terraform fmt, terraform validate, and terraform plan in CI
  • Add container image scanning
  • Add Terraform security scanning using Checkov or tfsec
  • Add manual approval before production-style terraform apply
  • Store Terraform state remotely in an S3 backend with state locking

Phase 4 β€” Observability

  • Add CloudWatch dashboards
  • Enable VPC Flow Logs
  • Add RDS performance monitoring
  • Add synthetic health checks
  • Forward logs to a SIEM or centralized logging platform
  • Track application errors, ALB target health, database connections, and ECS task failures

🚧 Current Limitations

Limitation Current Status
HTTPS Not implemented yet
Secrets Manager Database credentials are not fully managed through Secrets Manager yet
AWS WAF Not enabled yet
Autoscaling Limited autoscaling coverage
Region Design Currently designed as a single-region deployment
CI/CD No formal pipeline yet
Observability No full CloudWatch dashboard or centralized logging pipeline yet

These limitations are acceptable for the current version because the main goal is to prove private cross-VPC application-to-database connectivity first.


πŸš€ Why This Project Matters

This project demonstrates practical cloud engineering beyond a basic β€œhello world” deployment.

It shows how to combine networking, compute, containers, load balancing, security groups, private database access, and Terraform into one working AWS architecture.

The value of this project is not just that the application works. The value is that it proves the ability to troubleshoot real infrastructure problems across multiple layers.

Application
   ↓
Container
   ↓
EC2 Host
   ↓
Application Load Balancer
   ↓
Security Groups
   ↓
Route Tables
   ↓
VPC Peering
   ↓
RDS MySQL
   ↓
Terraform State and Dependencies

✨ What This Project Proves

Area Value Shown
AWS Networking Multi-VPC routing and private connectivity
Cloud Security Security group-based access control and private-by-default design
Compute EC2 and ECS on EC2 deployment patterns
Containers Docker image build, ECR push, and ECS runtime execution
Infrastructure as Code Terraform-managed AWS deployment
Troubleshooting Real debugging of ALB, ECS, RDS, ports, routes, and security groups
Application Integration Flask application successfully reaches RDS through private networking
Operational Access SSM-based private instance debugging without public SSH

♻️ Cleanup

To avoid ongoing AWS charges, destroy the deployed infrastructure when testing is complete.

terraform destroy

After Terraform finishes, verify that the following resources were removed:

  • Application Load Balancers
  • NAT Gateways
  • RDS instances
  • EC2 instances
  • ECS services
  • ECS container instances
  • ECR images
  • VPC peering connections
  • VPC routes and route tables
  • Security groups
  • CloudWatch log groups created during testing

NAT Gateways, RDS instances, and running EC2 instances can continue generating charges if they are left active.

⚠️ Cleanup Troubleshooting

If terraform destroy takes too long or appears stuck on a resource such as an ECS service, the service may still be draining or waiting on attached resources.

Example:

aws_ecs_service.app[0]: Still destroying...

If Terraform remains stuck for an extended period, manually delete the blocking resource from the AWS Console or AWS CLI, then re-run:

terraform destroy

If the resource was already deleted manually but Terraform is still stuck tracking it, remove that resource from the Terraform state.

First, find the resource name in state:

terraform state list

Then remove the deleted resource from state:

terraform state rm "resource_name_from_state"

Example:

terraform state rm "aws_ecs_service.app[0]"

After removing the stale resource from state, run destroy again:

terraform destroy
image

Beware, terraform is not perfect and your destroy will lock up, you need to delete those resources yourself.

Last thing, USE THE SCRIPTS, USE THE SCRIPTS, USE THE SCRIPTS

This allows Terraform to continue removing the remaining infrastructure cleanly.

Connect With Me

I am interested in cloud engineering, AWS infrastructure, DevOps, DevSecOps, platform engineering, and cloud security roles where I can help teams build secure, reliable, and automated systems.

About

Secure multi-tier AWS platform with ECS on EC2, ALB, RDS MySQL, VPC peering, Terraform, and private app-to-database connectivity.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages