Learn how to deploy a scalable, secure, and production-ready three-tier application with RDS PostgreSQL, Auto Scaling, and Application Load Balancer on AWS.
Building a production-ready three-tier application on AWS requires careful planning of networking, security, scalability, and cost optimization. In this comprehensive guide, we’ll deploy a complete three-tier architecture with RDS PostgreSQL, Auto Scaling Groups, and Application Load Balancer in the ap-south-1 region.
Our three-tier architecture consists of:
graph TB
Internet[Internet] --> IGW[Internet Gateway]
IGW --> ALB[Application Load Balancer]
ALB --> EC2-1[EC2 Instance 1]
ALB --> EC2-2[EC2 Instance 2]
EC2-1 --> RDS[RDS PostgreSQL Multi-AZ]
EC2-2 --> RDS
EC2-1 --> NAT[NAT Gateway]
EC2-2 --> NAT
NAT --> IGW
subgraph "Public Subnets"
ALB
NAT
end
subgraph "Private App Subnets"
EC2-1
EC2-2
end
subgraph "Private DB Subnets"
RDS
end
Before deploying the application, we need to set up a robust networking foundation:
VPC Details:
ap-south-1 (Asia Pacific - Mumbai)10.0.0.0/16ap-south-1a and ap-south-1bSubnet Configuration:
| Subnet Name | CIDR Block | AZ | Type | Purpose |
|---|---|---|---|---|
| Public-ALB-1 | 10.0.0.0/24 | ap-south-1a | Public | ALB |
| Public-ALB-2 | 10.0.1.0/24 | ap-south-1b | Public | ALB |
| Private-App-1 | 10.0.10.0/24 | ap-south-1a | Private | EC2 Instances |
| Private-App-2 | 10.0.11.0/24 | ap-south-1b | Private | EC2 Instances |
| Private-DB-1 | 10.0.20.0/24 | ap-south-1a | Private | RDS Primary |
| Private-DB-2 | 10.0.21.0/24 | ap-south-1b | Private | RDS Standby |
Create VPC
# Using AWS CLI (optional - can be done via console)
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --region ap-south-1
Create Subnets
Internet Gateway
0.0.0.0/0 from public subnets to IGWNAT Gateway
0.0.0.0/0 from private app subnets to NATRoute Tables
Security is paramount in production environments. We’ll implement defense-in-depth with layered security groups:
db-app-alb-sg)Inbound Rules:
- Type: HTTP
Protocol: TCP
Port: 80
Source: 0.0.0.0/0
- Type: HTTPS
Protocol: TCP
Port: 443
Source: 0.0.0.0/0
Outbound Rules:
- Type: All Traffic
Protocol: All
Port: All
Destination: 0.0.0.0/0
db-app-ec2-sg)Inbound Rules:
- Type: Custom TCP
Protocol: TCP
Port: 5000
Source: db-app-alb-sg
- Type: SSH
Protocol: TCP
Port: 22
Source: Your-IP/32 (for troubleshooting)
Outbound Rules:
- Type: All Traffic
Protocol: All
Port: All
Destination: 0.0.0.0/0
db-app-rds-sg)Inbound Rules:
- Type: PostgreSQL
Protocol: TCP
Port: 5432
Source: db-app-ec2-sg
Outbound Rules:
- Type: All Traffic
Protocol: All
Port: All
Destination: 0.0.0.0/0
Security Best Practices:
Before creating RDS, we’ll store database credentials securely:
Create Secret in Secrets Manager
{
"username": "myadmin",
"password": "your-strong-password-here",
"host": "placeholder",
"port": "5432",
"dbname": "myappdb"
}
Secret Configuration
my-app/db-credentialsap-south-1Production Configuration:
Engine: PostgreSQL 15.x
Instance Class: db.t3.medium (dev/test) or db.m5.large (production)
Storage:
Type: General Purpose SSD (gp3)
Size: 20 GiB (minimum)
Autoscaling: Enabled (max 1000 GiB)
Availability: Multi-AZ deployment
Encryption: Enabled (AWS managed key)
Backup:
Retention: 7 days
Window: No preference
Monitoring:
Performance Insights: Enabled
Enhanced Monitoring: Optional
Maintenance:
Auto minor version upgrade: Enabled
Window: No preference
Deletion Protection: Enabled
Key Production Features:
Update Secret with RDS Endpoint
host value in Secrets Managermy-app-db.abcdefghijk.ap-south-1.rds.amazonaws.comVerify Database Connectivity
-- Test connection and create initial schema if needed
SELECT version();
Create an IAM role with minimal permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:ap-south-1:ACCOUNT:secret:my-app/db-credentials-*"
}
]
}
import boto3
import json
import logging
from flask import Flask, jsonify
from sqlalchemy import create_engine, text
from sqlalchemy.pool import QueuePool
import os
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
def get_db_credentials():
"""Retrieve database credentials from AWS Secrets Manager"""
try:
secret_name = "my-app/db-credentials"
region_name = "ap-south-1"
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
response = client.get_secret_value(SecretId=secret_name)
secret = json.loads(response['SecretString'])
return secret
except Exception as e:
logger.error(f"Failed to retrieve credentials: {e}")
raise
def create_db_engine():
"""Create database engine with connection pooling"""
try:
creds = get_db_credentials()
db_url = f"postgresql://{creds['username']}:{creds['password']}@{creds['host']}:{creds['port']}/{creds['dbname']}"
engine = create_engine(
db_url,
poolclass=QueuePool,
pool_size=5,
max_overflow=10,
pool_pre_ping=True,
pool_recycle=3600,
echo=False
)
return engine
except Exception as e:
logger.error(f"Failed to create database engine: {e}")
raise
# Global engine instance
engine = create_db_engine()
@app.route('/')
def health_check():
"""Health check endpoint"""
try:
with engine.connect() as connection:
result = connection.execute(text('SELECT version()'))
version = result.scalar()
return jsonify({
"status": "healthy",
"database": "connected",
"postgresql_version": version
})
except Exception as e:
logger.error(f"Health check failed: {e}")
return jsonify({
"status": "unhealthy",
"error": str(e)
}), 500
@app.route('/api/status')
def api_status():
"""API status endpoint"""
return jsonify({
"service": "three-tier-app",
"version": "1.0.0",
"status": "operational"
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
#!/bin/bash
set -euxo pipefail
# Update system
yum update -y
# Install required packages
yum install -y python3-pip git htop
# Install Python dependencies
pip3 install --no-cache-dir \
flask==2.3.3 \
boto3==1.28.57 \
psycopg2-binary==2.9.7 \
SQLAlchemy==2.0.21 \
gunicorn==21.2.0
# Create application directory
mkdir -p /opt/app
cd /opt/app
# Create Flask application
cat << 'EOF' > /opt/app/app.py
# [Flask application code from above]
EOF
# Create Gunicorn configuration
cat << 'EOF' > /opt/app/gunicorn.conf.py
bind = "0.0.0.0:5000"
workers = 2
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
max_requests = 1000
max_requests_jitter = 100
preload_app = True
EOF
# Create systemd service
cat << 'EOF' > /etc/systemd/system/flaskapp.service
[Unit]
Description=Flask App with Gunicorn
After=network.target
[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory=/opt/app
ExecStart=/usr/local/bin/gunicorn -c gunicorn.conf.py app:app
Restart=always
RestartSec=3
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Set permissions
chown -R ec2-user:ec2-user /opt/app
chmod +x /opt/app/app.py
# Enable and start service
systemctl daemon-reload
systemctl enable flaskapp
systemctl start flaskapp
# Verify service status
systemctl status flaskapp
Name: db-app-tg
Protocol: HTTP
Port: 5000
VPC: your-vpc-id
Health Check:
Protocol: HTTP
Path: /
Port: 5000
Interval: 30 seconds
Timeout: 5 seconds
Healthy Threshold: 2
Unhealthy Threshold: 3
Name: db-app-alb
Scheme: Internet-facing
Type: Application Load Balancer
Subnets: [public-subnet-1, public-subnet-2]
Security Groups: [db-app-alb-sg]
Listeners:
- Protocol: HTTP
Port: 80
Default Action: Forward to db-app-tg
Launch Template: db-app-launch-template
VPC: your-vpc-id
Subnets: [private-app-subnet-1, private-app-subnet-2]
Load Balancer: db-app-alb
Target Group: db-app-tg
Health Checks: EC2 + ELB
Capacity:
Desired: 2
Minimum: 2
Maximum: 10
Scaling Policies:
- Type: Target Tracking
Metric: Average CPU Utilization
Target Value: 50%
Scale Out Cooldown: 300 seconds
Scale In Cooldown: 300 seconds
CloudWatch Metrics
CloudWatch Logs
CloudWatch Alarms
High CPU Utilization:
Metric: CPUUtilization
Threshold: 80%
Period: 5 minutes
Actions: SNS notification
Database Connection Errors:
Metric: DatabaseConnections
Threshold: 90%
Period: 1 minute
Actions: SNS notification
WAF (Web Application Firewall)
SSL/TLS Certificates
Secrets Rotation
Database Optimization
Caching
Auto Scaling Tuning
Monthly Cost Estimates (ap-south-1):
| Service | Configuration | Monthly Cost (USD) |
|---|---|---|
| RDS Multi-AZ | db.t3.medium | ~$45 |
| NAT Gateway | 1 instance | ~$32 |
| ALB | 1 instance | ~$16 |
| EC2 | 2x t2.micro | ~$16 |
| Total | ~$109 |
Scale Down Auto Scaling Group
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name db-app-asg \
--desired-capacity 0 \
--min-size 0 \
--max-size 0
Delete Load Balancer Components
Delete RDS Instance
aws rds delete-db-instance \
--db-instance-identifier my-app-db \
--skip-final-snapshot
Clean Up Secrets Manager
aws secretsmanager delete-secret \
--secret-id my-app/db-credentials \
--force-delete-without-recovery
Delete NAT Gateway
aws ec2 delete-nat-gateway --nat-gateway-id nat-xxxxx
Release Elastic IPs
aws ec2 release-address --allocation-id eipalloc-xxxxx
Delete VPC and Subnets
AWS Cost Explorer
Budget Alerts
Budget Name: Three-Tier App Budget
Amount: $150
Alert Thresholds: 80%, 100%
Symptoms:
Solutions:
# Check security groups
aws ec2 describe-security-groups --group-ids sg-xxxxx
# Verify RDS endpoint
aws rds describe-db-instances --db-instance-identifier my-app-db
# Test connectivity from EC2
telnet my-app-db.xxxxx.ap-south-1.rds.amazonaws.com 5432
Symptoms:
Solutions:
# Check target group health
aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:...
# Verify security group rules
aws ec2 describe-security-groups --group-ids sg-xxxxx
# Check application logs
sudo journalctl -u flaskapp -f
Symptoms:
Solutions:
# Check launch template
aws ec2 describe-launch-templates --launch-template-names db-app-launch-template
# Verify IAM role permissions
aws iam get-role --role-name ec2-secrets-reader-role
# Check ASG activity
aws autoscaling describe-scaling-activities --auto-scaling-group-name db-app-asg
# Check application status
curl http://your-alb-dns-name/
# Monitor logs
sudo journalctl -u flaskapp -f
# Check system resources
htop
df -h
free -h
# Test database connectivity
psql -h my-app-db.xxxxx.ap-south-1.rds.amazonaws.com -U myadmin -d myappdb
This comprehensive guide covers deploying a production-ready three-tier application on AWS with:
The architecture provides a solid foundation for production workloads while maintaining security, scalability, and cost-effectiveness. Regular monitoring, maintenance, and optimization will ensure continued performance and reliability.
Remember to always test in a non-production environment first and follow AWS Well-Architected Framework principles for production deployments.