Deploy a Production-Ready Three-Tier RDS Application on AWS: Complete Guide

Learn how to deploy a scalable, secure, and production-ready three-tier application with RDS PostgreSQL, Auto Scaling, and Application Load Balancer on AWS.

Deploy a Production-Ready Three-Tier RDS Application on AWS: Complete Guide

Table of Contents

Deploy a Production-Ready Three-Tier RDS Application on AWS: Complete Guide

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.

Table of Contents

  1. Architecture Overview
  2. Prerequisites and VPC Setup
  3. Security Groups Configuration
  4. Database Setup with RDS PostgreSQL
  5. Application Deployment
  6. Load Balancer and Auto Scaling
  7. Production Considerations
  8. Cost Optimization and Cleanup
  9. Troubleshooting

Architecture Overview

Our three-tier architecture consists of:

  • Presentation Tier: Application Load Balancer (ALB) in public subnets
  • Application Tier: EC2 instances in private subnets with Auto Scaling
  • Data Tier: RDS PostgreSQL Multi-AZ in private subnets
  • Security: Layered security groups and AWS Secrets Manager
  • Networking: VPC with public/private subnets, NAT Gateway, and Internet Gateway
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

Prerequisites and VPC Setup

VPC Configuration

Before deploying the application, we need to set up a robust networking foundation:

VPC Details:

  • Region: ap-south-1 (Asia Pacific - Mumbai)
  • VPC CIDR: 10.0.0.0/16
  • Availability Zones: ap-south-1a and ap-south-1b

Subnet Configuration:

Subnet NameCIDR BlockAZTypePurpose
Public-ALB-110.0.0.0/24ap-south-1aPublicALB
Public-ALB-210.0.1.0/24ap-south-1bPublicALB
Private-App-110.0.10.0/24ap-south-1aPrivateEC2 Instances
Private-App-210.0.11.0/24ap-south-1bPrivateEC2 Instances
Private-DB-110.0.20.0/24ap-south-1aPrivateRDS Primary
Private-DB-210.0.21.0/24ap-south-1bPrivateRDS Standby

Step-by-Step VPC Setup

  1. 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
    
  2. Create Subnets

    • Enable auto-assign public IPv4 for public subnets
    • Ensure proper AZ distribution for high availability
  3. Internet Gateway

    • Create and attach to VPC
    • Route 0.0.0.0/0 from public subnets to IGW
  4. NAT Gateway

    • Allocate Elastic IP
    • Create NAT Gateway in public subnet
    • Route 0.0.0.0/0 from private app subnets to NAT
  5. Route Tables

    • Public-RT: Routes to Internet Gateway
    • App-Private-RT: Routes to NAT Gateway
    • DB-Private-RT: Local routes only

Security Groups Configuration

Security is paramount in production environments. We’ll implement defense-in-depth with layered security groups:

1. ALB Security Group (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

2. EC2 Security Group (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

3. RDS Security Group (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:

  • No direct internet access to EC2 or RDS
  • ALB is the only public entry point
  • Principle of least privilege for all security groups
  • Regular security group audits

Database Setup with RDS PostgreSQL

AWS Secrets Manager Configuration

Before creating RDS, we’ll store database credentials securely:

  1. Create Secret in Secrets Manager

    {
      "username": "myadmin",
      "password": "your-strong-password-here",
      "host": "placeholder",
      "port": "5432",
      "dbname": "myappdb"
    }
    
  2. Secret Configuration

    • Secret Name: my-app/db-credentials
    • Encryption: AWS managed key
    • Region: ap-south-1
    • Rotation: Disabled (can be enabled for production)

RDS PostgreSQL Multi-AZ Setup

Production 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:

  • Multi-AZ: Automatic failover and high availability
  • Encryption: Data encrypted at rest and in transit
  • Backups: Automated daily backups with point-in-time recovery
  • Monitoring: Performance Insights for query optimization
  • Security: VPC isolation and security group restrictions

Post-Creation Steps

  1. Update Secret with RDS Endpoint

    • Retrieve the RDS endpoint from console
    • Update the host value in Secrets Manager
    • Example: my-app-db.abcdefghijk.ap-south-1.rds.amazonaws.com
  2. Verify Database Connectivity

    -- Test connection and create initial schema if needed
    SELECT version();
    

Application Deployment

IAM Role for EC2

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-*"
    }
  ]
}

Flask Application with Production Features

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)

EC2 Launch Template with Production Features

#!/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

Load Balancer and Auto Scaling

Target Group Configuration

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

Application Load Balancer Setup

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

Auto Scaling Group Configuration

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

Production Considerations

Monitoring and Logging

  1. CloudWatch Metrics

    • EC2 CPU, Memory, Network
    • RDS Database connections, CPU, Storage
    • ALB Request count, Response time
  2. CloudWatch Logs

    • Application logs
    • System logs
    • Database logs
  3. 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
    

Security Enhancements

  1. WAF (Web Application Firewall)

    • Protect against common attacks
    • Rate limiting
    • Geographic restrictions
  2. SSL/TLS Certificates

    • Use AWS Certificate Manager
    • HTTPS redirect
    • HSTS headers
  3. Secrets Rotation

    • Enable automatic password rotation
    • Update application to handle rotation

Performance Optimization

  1. Database Optimization

    • Read replicas for read-heavy workloads
    • Connection pooling
    • Query optimization
  2. Caching

    • ElastiCache for Redis/Memcached
    • Application-level caching
    • CDN for static content
  3. Auto Scaling Tuning

    • Custom metrics for scaling
    • Predictive scaling
    • Scheduled scaling

Cost Optimization and Cleanup

Cost Analysis

Monthly Cost Estimates (ap-south-1):

ServiceConfigurationMonthly Cost (USD)
RDS Multi-AZdb.t3.medium~$45
NAT Gateway1 instance~$32
ALB1 instance~$16
EC22x t2.micro~$16
Total~$109

Cleanup Procedures

Phase 1: Application and Database Cleanup

  1. 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
    
  2. Delete Load Balancer Components

    • Remove target group from ALB listener
    • Delete target group
    • Delete ALB
  3. Delete RDS Instance

    aws rds delete-db-instance \
      --db-instance-identifier my-app-db \
      --skip-final-snapshot
    
  4. Clean Up Secrets Manager

    aws secretsmanager delete-secret \
      --secret-id my-app/db-credentials \
      --force-delete-without-recovery
    

Phase 2: Networking Cleanup

  1. Delete NAT Gateway

    aws ec2 delete-nat-gateway --nat-gateway-id nat-xxxxx
    
  2. Release Elastic IPs

    aws ec2 release-address --allocation-id eipalloc-xxxxx
    
  3. Delete VPC and Subnets

    • Delete all subnets
    • Detach and delete Internet Gateway
    • Delete route tables
    • Delete VPC

Cost Monitoring

  1. AWS Cost Explorer

    • Set up cost alerts
    • Monitor daily spending
    • Analyze cost by service
  2. Budget Alerts

    Budget Name: Three-Tier App Budget
    Amount: $150
    Alert Thresholds: 80%, 100%
    

Troubleshooting

Common Issues and Solutions

1. Database Connection Failures

Symptoms:

  • Application returns database connection errors
  • Health checks failing

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

2. Load Balancer Health Check Failures

Symptoms:

  • Targets showing as unhealthy
  • 502 Bad Gateway errors

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

3. Auto Scaling Issues

Symptoms:

  • Instances not launching
  • Scaling not responding to metrics

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

Monitoring Commands

# 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

Conclusion

This comprehensive guide covers deploying a production-ready three-tier application on AWS with:

  • High Availability: Multi-AZ RDS, Auto Scaling, and load balancing
  • Security: Layered security groups, Secrets Manager, and VPC isolation
  • Scalability: Auto Scaling Groups with target tracking policies
  • Monitoring: CloudWatch metrics, logs, and alarms
  • Cost Optimization: Proper cleanup procedures and cost monitoring

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.

Next Steps

  1. Implement CI/CD: Set up automated deployment pipelines
  2. Add Monitoring: Implement comprehensive observability
  3. Security Hardening: Regular security audits and updates
  4. Performance Tuning: Optimize based on actual usage patterns
  5. Disaster Recovery: Implement backup and recovery procedures

Remember to always test in a non-production environment first and follow AWS Well-Architected Framework principles for production deployments.

Table of Contents