AWS Production-Grade Migration: Building the Legacy Source Environment (Part 1)

Learn how to build a production-grade legacy source environment in AWS for cloud migration. This comprehensive guide covers VPC setup, EC2 web servers, RDS PostgreSQL databases, and database-connected applications.

AWS Production-Grade Migration: Building the Legacy Source Environment (Part 1)

Table of Contents

AWS Production-Grade Migration: Building the Legacy Source Environment (Part 1)

Overview

In this comprehensive guide, we’ll create a simulated “on-premises” environment within AWS that represents the legacy infrastructure we’ll be migrating FROM. This production-grade setup includes a VPC with a web server and a PostgreSQL database with realistic sample data, perfectly designed for learning cloud migration strategies.

Estimated Time: 45-60 minutes
Region: ap-south-1 (Mumbai)
Difficulty: Intermediate


Why This Matters for Cloud Migration

Before diving into AWS Application Migration Service (MGN) and Database Migration Service (DMS), it’s crucial to understand the source environment. This hands-on approach gives you:

  • Real-world experience with legacy infrastructure patterns
  • Production-grade networking with proper VPC design
  • Database connectivity that mirrors actual enterprise applications
  • Security best practices for multi-tier architectures

Prerequisites Checklist

Before we begin, ensure you have:

  • AWS Account with administrative access
  • AWS Console access to ap-south-1 region
  • Your current public IP address (for SSH access) - Find it at whatismyipaddress.com
  • SSH client installed (PuTTY for Windows, or native SSH for Mac/Linux)
  • Basic understanding of VPC networking concepts

Cost Warning

⚠️ Resources that will incur charges:

  • EC2 t3.medium instance: $0.0232/hour ($17.00/month)
  • RDS db.t3.micro instance: $0.017/hour ($12.41/month)
  • EBS volumes (automatically created): ~$0.10/GB/month

Estimated Phase 1 Cost: ~$0.70-1.40 per day while running


Architecture Overview

Our legacy environment will consist of:

┌─────────────────────────────────────────────────────────┐
│  on-prem-vpc (10.0.0.0/16)                             │
│                                                          │
│  ┌────────────────────────────────────────────┐        │
│  │ Public Subnet (10.0.1.0/24) - ap-south-1a │        │
│  │                                              │        │
│  │  ┌──────────────────────────────────┐      │        │
│  │  │ EC2 Web Server                    │      │        │
│  │  │ - Apache httpd                    │      │        │
│  │  │ - PostgreSQL client               │      │        │
│  │  │ - Public IP: xxx.xxx.xxx.xxx     │      │        │
│  │  └──────────────────────────────────┘      │        │
│  └────────────────────────────────────────────┘        │
│                                                          │
│  ┌────────────────────────────────────────────┐        │
│  │ Private Subnet (10.0.2.0/24) - ap-south-1a│        │
│  │                                              │        │
│  │  ┌──────────────────────────────────┐      │        │
│  │  │ RDS PostgreSQL (legacy-db)       │      │        │
│  │  │ - Single-AZ                       │      │        │
│  │  │ - db.t3.micro                     │      │        │
│  │  │ - Port: 5432                      │      │        │
│  │  └──────────────────────────────────┘      │        │
│  └────────────────────────────────────────────┘        │
│                                                          │
│  ┌────────────────────────────────────────────┐        │
│  │ Private Subnet (10.0.3.0/24) - ap-south-1b│        │
│  │ (Required for DB subnet group)             │        │
│  └────────────────────────────────────────────┘        │
│                                                          │
│  Internet Gateway ←→ Public Subnet                      │
└─────────────────────────────────────────────────────────┘

Step 1: Create the Source VPC

1.1 Navigate to VPC Console

  1. Sign in to AWS Console
  2. Ensure you’re in the ap-south-1 region (top-right corner)
  3. Go to Services → VPC** (or search “VPC” in the search bar)

1.2 Create VPC

  1. Click Your VPCs in the left sidebar
  2. Click Create VPC (orange button)
  3. Configure the VPC:
    • Resources to create: VPC only
    • Name tag: on-prem-vpc
    • IPv4 CIDR block: 10.0.0.0/16
    • IPv6 CIDR block: No IPv6 CIDR block
    • Tenancy: Default
  4. Click Create VPC
  5. 📝 Note down the VPC ID (e.g., vpc-0abc123def456)

Step 2: Create Subnets

2.1 Create Public Subnet

  1. Click Subnets in the left sidebar
  2. Click Create subnet
  3. Configure:
    • VPC ID: Select on-prem-vpc
    • Subnet name: on-prem-public-subnet-1a
    • Availability Zone: ap-south-1a
    • IPv4 CIDR block: 10.0.1.0/24
  4. Click Create subnet
  5. 📝 Note down the Subnet ID (e.g., subnet-0abc123)

2.2 Create Private Subnet (First AZ)

  1. Click Create subnet again
  2. Configure:
    • VPC ID: Select on-prem-vpc
    • Subnet name: on-prem-private-subnet-1a
    • Availability Zone: ap-south-1a
    • IPv4 CIDR block: 10.0.2.0/24
  3. Click Create subnet
  4. 📝 Note down the Subnet ID (e.g., subnet-0def456)

2.3 Create Second Private Subnet (Required for RDS)

Important: AWS now requires DB subnet groups to have subnets in at least 2 Availability Zones, even for single-AZ databases.

  1. Click Create subnet again
  2. Configure:
    • VPC ID: Select on-prem-vpc
    • Subnet name: on-prem-private-subnet-1b
    • Availability Zone: ap-south-1b
    • IPv4 CIDR block: 10.0.3.0/24
  3. Click Create subnet
  4. 📝 Note down the Subnet ID (e.g., subnet-0ghi789)

Step 3: Configure Internet Gateway

3.1 Create Internet Gateway

  1. Click Internet Gateways in the left sidebar
  2. Click Create internet gateway
  3. Configure:
    • Name tag: on-prem-igw
  4. Click Create internet gateway
  5. You’ll see a success message with an Attach to VPC button

3.2 Attach to VPC

  1. Click Attach to a VPC (or select the IGW and click Actions → Attach to VPC)
  2. Available VPCs: Select on-prem-vpc
  3. Click Attach internet gateway

Step 4: Configure Route Tables

4.1 Create Public Route Table

  1. Click Route Tables in the left sidebar
  2. Click Create route table
  3. Configure:
    • Name: on-prem-public-rt
    • VPC: Select on-prem-vpc
  4. Click Create route table

4.2 Add Internet Route

  1. Select the newly created on-prem-public-rt
  2. Go to the Routes tab (bottom panel)
  3. Click Edit routes
  4. Click Add route
  5. Configure the new route:
    • Destination: 0.0.0.0/0
    • Target: Internet Gateway → Select on-prem-igw
  6. Click Save changes

4.3 Associate with Public Subnet

  1. Still on on-prem-public-rt, go to Subnet associations tab
  2. Click Edit subnet associations
  3. Check the box for on-prem-public-subnet-1a
  4. Click Save associations

4.4 Configure Private Route Table

  1. Go back to Route Tables
  2. Find the default route table for on-prem-vpc (it won’t have a name)
  3. Select it and click the Name column to edit
  4. Name it: on-prem-private-rt
  5. Go to Subnet associations tab
  6. Click Edit subnet associations
  7. Associate BOTH private subnets:
    • Check on-prem-private-subnet-1a (10.0.2.0/24)
    • Check on-prem-private-subnet-1b (10.0.3.0/24)
  8. Click Save associations

Step 5: Create Security Groups

5.1 Create Web Server Security Group

  1. Click Security Groups in the left sidebar

  2. Click Create security group

  3. Configure:

    • Security group name: on-prem-web-sg
    • Description: Security group for on-premises web server
    • VPC: Select on-prem-vpc
  4. Inbound rules - Click Add rule for each:

    Rule 1 (SSH):

    • Type: SSH
    • Protocol: TCP
    • Port range: 22
    • Source: My IP (it will auto-detect your IP)
    • Description: SSH from my IP

    Rule 2 (HTTP):

    • Type: HTTP
    • Protocol: TCP
    • Port range: 80
    • Source: 0.0.0.0/0
    • Description: HTTP from anywhere
  5. Outbound rules: Leave default (All traffic to 0.0.0.0/0)

  6. Click Create security group

  7. 📝 Note down the Security Group ID (e.g., sg-0abc123web)

5.2 Create Database Security Group

  1. Click Create security group again

  2. Configure:

    • Security group name: on-prem-db-sg
    • Description: Security group for on-premises database
    • VPC: Select on-prem-vpc
  3. Inbound rules - Click Add rule:

    • Type: PostgreSQL
    • Protocol: TCP
    • Port range: 5432
    • Source: Custom → Select the on-prem-web-sg security group ID
    • Description: PostgreSQL from web server
  4. Outbound rules: Leave default

  5. Click Create security group

  6. 📝 Note down the Security Group ID (e.g., sg-0def456db)


Step 6: Create EC2 Key Pair

  1. Go to ServicesEC2
  2. Click Key Pairs in the left sidebar (under Network & Security)
  3. Click Create key pair
  4. Configure:
    • Name: on-prem-web-key
    • Key pair type: RSA
    • Private key file format:
      • .pem for Mac/Linux
      • .ppk for Windows (PuTTY)
  5. Click Create key pair
  6. The private key will download automatically - save it securely!
  7. For Mac/Linux, set permissions: chmod 400 on-prem-web-key.pem

Step 7: Launch EC2 Web Server

7.1 Launch Instance

  1. Go to EC2 Dashboard
  2. Click Launch Instance (orange button)

7.2 Configure Instance Details

Step 1: Name and tags

  • Name: on-prem-web-server

Step 2: Application and OS Images

  • Quick Start: Amazon Linux
  • Amazon Machine Image (AMI): Amazon Linux 2023 AMI (should be the default)
  • Architecture: 64-bit (x86)

Step 3: Instance type

  • Instance type: t3.medium

Step 4: Key pair

  • Key pair name: Select on-prem-web-key

Step 5: Network settings

  • Click Edit
  • VPC: Select on-prem-vpc
  • Subnet: Select on-prem-public-subnet-1a
  • Auto-assign public IP: Enable
  • Firewall (security groups): Select existing security group
  • Security groups: Select on-prem-web-sg

Step 6: Configure storage

  • Leave default (8 GB gp3)

Step 7: Advanced details

  • Scroll down to User data (at the bottom)
  • Paste the following script:
#!/bin/bash
# Update system
yum update -y

# Install Apache web server
yum install -y httpd

# Install PostgreSQL 16 client
yum install -y postgresql16

# Start and enable Apache
systemctl start httpd
systemctl enable httpd

# Create a simple homepage
cat > /var/www/html/index.html <<'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>On-Premises Web Server</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 50px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        .container {
            background: rgba(255, 255, 255, 0.1);
            padding: 30px;
            border-radius: 10px;
            max-width: 600px;
            margin: 0 auto;
        }
        h1 { margin-top: 0; }
        .status {
            background: rgba(255, 255, 255, 0.2);
            padding: 15px;
            border-radius: 5px;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🏢 On-Premises Web Server</h1>
        <p><strong>Environment:</strong> Legacy/Source</p>
        <p><strong>Status:</strong> Running</p>
        <div class="status">
            <p><strong>Server Info:</strong></p>
            <ul>
                <li>Web Server: Apache httpd</li>
                <li>VPC: on-prem-vpc (10.0.0.0/16)</li>
                <li>Subnet: Public (10.0.1.0/24)</li>
            </ul>
        </div>
        <p style="margin-top: 30px; font-size: 0.9em; opacity: 0.8;">
            This server will be migrated to AWS using Application Migration Service (MGN)
        </p>
    </div>
</body>
</html>
EOF

# Set permissions
chmod 644 /var/www/html/index.html
chown apache:apache /var/www/html/index.html

7.3 Launch

  1. Review all settings in the Summary panel on the right
  2. Click Launch instance
  3. Wait for the success message
  4. Click View all instances
  5. Wait for Instance State to show “Running” and Status Check to show “2/2 checks passed” (this may take 2-3 minutes)
  6. 📝 Note down:
    • Instance ID: (e.g., i-0abc123def456)
    • Public IPv4 address: (e.g., 13.126.45.67)
    • Private IPv4 address: (e.g., 10.0.1.25)

Step 8: Verify Web Server

8.1 Test HTTP Access

  1. Copy the Public IPv4 address of your EC2 instance
  2. Open a web browser
  3. Navigate to: http://YOUR_PUBLIC_IP
  4. You should see the “On-Premises Web Server” page with purple gradient
  5. ✅ If you see this page, your web server is working correctly!

8.2 SSH into the Instance

For Mac/Linux:

ssh -i on-prem-web-key.pem ec2-user@YOUR_PUBLIC_IP

For Windows (PuTTY):

  1. Open PuTTY
  2. Host Name: ec2-user@YOUR_PUBLIC_IP
  3. Connection → SSH → Auth → Credentials: Browse and select your .ppk file
  4. Click Open

8.3 Verify PostgreSQL Client

Once connected via SSH, run:

psql --version

Expected output: psql (PostgreSQL) 16.x


Step 9: Create RDS Database Subnet Group

Before creating the RDS instance, we need a DB subnet group.

9.1 Navigate to RDS Console

  1. Go to ServicesRDS
  2. Click Subnet groups in the left sidebar
  3. Click Create DB subnet group

9.2 Configure Subnet Group

  • Name: on-prem-db-subnet-group
  • Description: Subnet group for on-premises legacy database
  • VPC: Select on-prem-vpc

Note: AWS has updated the interface and now requires subnets in at least 2 Availability Zones. Follow these steps:

  1. Availability Zone group: Select ap-south-1-zg-1 (or any available group)
  2. Subnets: After selecting the AZ group, choose BOTH private subnets:
    • on-prem-private-subnet-1a (10.0.2.0/24) - ap-south-1a
    • on-prem-private-subnet-1b (10.0.3.0/24) - ap-south-1b

Important: AWS now requires at least 2 AZs for DB subnet groups, even for single-AZ databases. This ensures better availability planning.

  • Click Create

Step 10: Launch RDS PostgreSQL Database

10.1 Create Database

  1. In the RDS console, click Databases in the left sidebar
  2. Click Create database (orange button)

10.2 Engine Configuration

  • Choose a database creation method: Standard create
  • Engine type: PostgreSQL
  • Engine Version: PostgreSQL 16.x (or latest 16.x)
  • Templates: Dev/Test (to keep it simple and cost-effective)

10.3 Settings

  • DB instance identifier: legacy-db
  • Master username: postgres (default, leave as is)
  • Master password: Create a strong password (e.g., LegacyDB2024!)
  • Confirm password: Re-enter your password
  • 📝 Note down this password securely!

10.4 Instance Configuration

  • DB instance class: Burstable classes
    • Select: db.t3.micro (2 vCPUs, 1 GB RAM)

10.5 Availability and Durability

Important: Choose the deployment option based on your needs:

  • Single-AZ DB instance deployment (1 instance)RECOMMENDED

    • Uptime: 99.5%
    • Cost: ~$12/month
    • Use case: Perfect for learning, demos, and migration testing
    • Why choose this: Cost-effective for this exercise, matches legacy environment simplicity
  • Multi-AZ DB instance deployment (2 instances)

    • Uptime: 99.95%
    • Cost: ~$24/month
    • Use case: Production workloads requiring high availability
  • Multi-AZ DB cluster deployment (3 instances)

    • Uptime: 99.95%
    • Cost: ~$36+/month
    • Use case: High-performance production workloads with read replicas

Select: Single-AZ DB instance deployment (1 instance)

10.6 Storage

  • Storage type: General Purpose SSD (gp3)
  • Allocated storage: 20 GB (minimum)
  • Storage autoscaling: Uncheck “Enable storage autoscaling” (to control costs)

10.7 Connectivity

  • Compute resource: Don’t connect to an EC2 compute resource
  • VPC: Select on-prem-vpc
  • DB subnet group: Select on-prem-db-subnet-group
  • Public access: No
  • VPC security group: Choose existing
    • Remove the default security group
    • Select on-prem-db-sg
  • Availability Zone: ap-south-1a
  • Database port: 5432 (default)

10.8 Database Authentication

  • Database authentication: Password authentication

10.9 Additional Configuration

  • Click Additional configuration to expand
  • Initial database name: legacydb (⚠️ Important! This creates the database)
  • DB parameter group: default.postgres16
  • Backup:
    • Uncheck “Enable automated backups” (to save costs for this demo)
  • Encryption: Uncheck “Enable encryption” (optional, for simplicity)
  • Log exports: Leave unchecked
  • Maintenance: Enable auto minor version upgrade
  • Deletion protection: Uncheck (so we can easily delete later)

10.10 Create Database

  1. Review the Estimated monthly costs on the right (should be ~$12-15)
  2. Click Create database
  3. You’ll see a success banner
  4. Wait for the database status to change from “Creating” to “Available” (this takes 5-10 minutes)
  5. ☕ Good time for a coffee break!

10.11 Note Database Endpoint

  1. Once the status is “Available”, click on the database name legacy-db
  2. Scroll down to Connectivity & security
  3. 📝 Note down the Endpoint: (e.g., legacy-db.abc123xyz.ap-south-1.rds.amazonaws.com)

Step 11: Connect to Database and Create Sample Data

11.1 SSH into Web Server

Connect to your EC2 web server using SSH (as done in Step 8.2)

11.2 Connect to Database

psql --host=YOUR_RDS_ENDPOINT --port=5432 --username=postgres --dbname=legacydb

Replace YOUR_RDS_ENDPOINT with the actual endpoint from Step 10.11.

When prompted, enter the master password you created in Step 10.3.

Expected output:

psql (16.x)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

legacydb=>

11.3 Create Sample Table and Data

Copy and paste the following SQL commands:

-- Create users table
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert sample data
INSERT INTO users (username, email) VALUES
    ('amodh', 'amodh@onprem.local'),
    ('john_doe', 'john@onprem.local'),
    ('jane_smith', 'jane@onprem.local'),
    ('admin', 'admin@onprem.local');

-- Create a products table for more realistic data
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    stock_quantity INTEGER DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert sample products
INSERT INTO products (product_name, price, stock_quantity) VALUES
    ('Laptop', 45999.99, 15),
    ('Mouse', 599.99, 50),
    ('Keyboard', 1299.99, 30),
    ('Monitor', 12999.99, 20),
    ('Headphones', 2499.99, 40);

-- Create an orders table
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert sample orders
INSERT INTO orders (user_id, product_id, quantity) VALUES
    (1, 1, 1),
    (1, 2, 2),
    (2, 3, 1),
    (3, 4, 1),
    (4, 5, 3);

11.4 Verify Data

-- Check users
SELECT * FROM users;

-- Check products
SELECT * FROM products;

-- Check orders with details
SELECT
    o.id,
    u.username,
    p.product_name,
    o.quantity,
    o.order_date
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id;

-- Exit psql
\q

Expected output for users:

 id | username   | email                | created_at
----+------------+----------------------+---------------------
  1 | amodh      | amodh@onprem.local  | 2025-10-17 10:30:00
  2 | john_doe   | john@onprem.local   | 2025-10-17 10:30:00
  3 | jane_smith | jane@onprem.local   | 2025-10-17 10:30:00
  4 | admin      | admin@onprem.local  | 2025-10-17 10:30:00
(4 rows)

Step 12: Create a Database-Connected Application

Let’s enhance our web application to connect to the database.

12.1 Create Python Application

While still SSH’d into the web server:

# Install Python and pip
sudo yum install -y python3 python3-pip

# Install psycopg2 (PostgreSQL adapter for Python)
sudo pip3 install psycopg2-binary

# Create application directory
sudo mkdir -p /var/www/cgi-bin
cd /var/www/cgi-bin

# Create the database application
sudo cat > /var/www/cgi-bin/db_app.py <<'PYEOF'
#!/usr/bin/env python3
import cgi
import cgitb
import psycopg2
import os

cgitb.enable()

# Database configuration
DB_HOST = "YOUR_RDS_ENDPOINT"
DB_NAME = "legacydb"
DB_USER = "postgres"
DB_PASS = "YOUR_DB_PASSWORD"

print("Content-Type: text/html\n")
print("""
<!DOCTYPE html>
<html>
<head>
    <title>Legacy Application - Database View</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 30px;
            background: #f5f5f5;
        }
        .container {
            background: white;
            padding: 30px;
            border-radius: 10px;
            max-width: 900px;
            margin: 0 auto;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 { color: #333; border-bottom: 3px solid #667eea; padding-bottom: 10px; }
        h2 { color: #667eea; margin-top: 30px; }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 15px;
        }
        th {
            background: #667eea;
            color: white;
            padding: 12px;
            text-align: left;
        }
        td {
            padding: 10px;
            border-bottom: 1px solid #ddd;
        }
        tr:hover { background: #f0f0f0; }
        .status {
            background: #d4edda;
            color: #155724;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
        .error {
            background: #f8d7da;
            color: #721c24;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🏢 Legacy On-Premises Application</h1>
""")

try:
    # Connect to database
    conn = psycopg2.connect(
        host=DB_HOST,
        database=DB_NAME,
        user=DB_USER,
        password=DB_PASS
    )
    cur = conn.cursor()

    print('<div class="status">✅ Database Connection: Successful</div>')

    # Fetch users
    print("<h2>Users Table</h2>")
    cur.execute("SELECT id, username, email, created_at FROM users ORDER BY id")
    users = cur.fetchall()

    print("<table>")
    print("<tr><th>ID</th><th>Username</th><th>Email</th><th>Created At</th></tr>")
    for user in users:
        print(f"<tr><td>{user[0]}</td><td>{user[1]}</td><td>{user[2]}</td><td>{user[3]}</td></tr>")
    print("</table>")

    # Fetch products
    print("<h2>Products Table</h2>")
    cur.execute("SELECT id, product_name, price, stock_quantity FROM products ORDER BY id")
    products = cur.fetchall()

    print("<table>")
    print("<tr><th>ID</th><th>Product</th><th>Price (₹)</th><th>Stock</th></tr>")
    for product in products:
        print(f"<tr><td>{product[0]}</td><td>{product[1]}</td><td>{product[2]}</td><td>{product[3]}</td></tr>")
    print("</table>")

    # Fetch orders with JOIN
    print("<h2>Recent Orders</h2>")
    cur.execute("""
        SELECT o.id, u.username, p.product_name, o.quantity, o.order_date
        FROM orders o
        JOIN users u ON o.user_id = u.id
        JOIN products p ON o.product_id = p.id
        ORDER BY o.order_date DESC
    """)
    orders = cur.fetchall()

    print("<table>")
    print("<tr><th>Order ID</th><th>Customer</th><th>Product</th><th>Qty</th><th>Date</th></tr>")
    for order in orders:
        print(f"<tr><td>{order[0]}</td><td>{order[1]}</td><td>{order[2]}</td><td>{order[3]}</td><td>{order[4]}</td></tr>")
    print("</table>")

    cur.close()
    conn.close()

except Exception as e:
    print(f'<div class="error">❌ Database Error: {str(e)}</div>')

print("""
        <p style="margin-top: 40px; color: #666; text-align: center;">
            This legacy application will be migrated using AWS MGN and DMS
        </p>
    </div>
</body>
</html>
""")
PYEOF

12.2 Update Configuration

Replace the placeholders in the script:

# Replace with your actual values
sudo sed -i 's|YOUR_RDS_ENDPOINT|YOUR_ACTUAL_ENDPOINT|g' /var/www/cgi-bin/db_app.py
sudo sed -i 's|YOUR_DB_PASSWORD|YOUR_ACTUAL_PASSWORD|g' /var/www/cgi-bin/db_app.py

# Make it executable
sudo chmod +x /var/www/cgi-bin/db_app.py

12.3 Configure Apache for CGI

# Enable CGI module
sudo sed -i 's|#LoadModule cgid_module modules/mod_cgid.so|LoadModule cgid_module modules/mod_cgid.so|g' /etc/httpd/conf.modules.d/01-cgi.conf

# Configure CGI directory
sudo tee -a /etc/httpd/conf/httpd.conf > /dev/null <<'EOF'

# CGI Configuration
<Directory "/var/www/cgi-bin">
    AllowOverride None
    Options +ExecCGI
    AddHandler cgi-script .py
    Require all granted
</Directory>

ScriptAlias /cgi-bin/ /var/www/cgi-bin/
EOF

# Restart Apache
sudo systemctl restart httpd

12.4 Test the Application

  1. Open your browser
  2. Navigate to: http://YOUR_PUBLIC_IP/cgi-bin/db_app.py
  3. You should see a clean interface showing:
    • ✅ Database Connection: Successful
    • Users table with 4 users
    • Products table with 5 products
    • Recent Orders with JOINed data

✅ If you see this data, your application is successfully connected to the database!


Step 13: Final Verification Checklist

Before proceeding to Phase 2, verify all components:

  • VPC on-prem-vpc created with CIDR 10.0.0.0/16
  • Public subnet (10.0.1.0/24) and private subnets (10.0.2.0/24, 10.0.3.0/24) exist
  • Internet Gateway attached and routing configured
  • EC2 web server running and accessible via HTTP
  • Simple HTML page loads at http://PUBLIC_IP/
  • Database application loads at http://PUBLIC_IP/cgi-bin/db_app.py
  • RDS PostgreSQL database legacy-db is available
  • Can connect to database from EC2 using psql
  • Sample data exists in users, products, and orders tables
  • Security groups properly configured (SSH, HTTP, PostgreSQL)

Resource Inventory (Note These Down!)

📝 Save these values for Phase 3 and Phase 4:

ResourceValueNotes
VPC IDvpc-xxxxxxxxon-prem-vpc
Public Subnet IDsubnet-xxxxxxxx10.0.1.0/24
Private Subnet 1 IDsubnet-xxxxxxxx10.0.2.0/24
Private Subnet 2 IDsubnet-xxxxxxxx10.0.3.0/24
Web Server Instance IDi-xxxxxxxxSource for MGN
Web Server Public IPx.x.x.xFor testing
Web Server Private IP10.0.1.xInternal IP
Web SG IDsg-xxxxxxxxon-prem-web-sg
DB SG IDsg-xxxxxxxxon-prem-db-sg
RDS Endpointlegacy-db.xxx.ap-south-1.rds.amazonaws.comSource for DMS
DB UsernamepostgresMaster username
DB Password******Keep secure!
DB NamelegacydbInitial database

Troubleshooting

Issue: Cannot SSH into EC2 instance

Solution:

  • Verify your security group allows SSH (port 22) from your current IP
  • Check if your IP changed (use whatismyipaddress.com)
  • Ensure the EC2 instance has a public IP
  • Verify the key pair permissions: chmod 400 your-key.pem

Issue: Web page not loading

Solution:

  • Check if instance is running and passed status checks
  • Verify security group allows HTTP (port 80) from 0.0.0.0/0
  • Check Apache status: sudo systemctl status httpd
  • View Apache logs: sudo tail -f /var/log/httpd/error_log

Issue: Cannot connect to database

Solution:

  • Verify RDS status is “Available”
  • Check database security group allows PostgreSQL (5432) from web server SG
  • Ensure you’re connecting from the EC2 instance (not your local machine)
  • Verify the endpoint, username, and password are correct
  • Check if database is in the same VPC

Issue: Database application shows error

Solution:

  • Verify psycopg2 is installed: pip3 list | grep psycopg2
  • Check Python script permissions: ls -l /var/www/cgi-bin/db_app.py
  • Check Apache error logs: sudo tail -f /var/log/httpd/error_log
  • Verify database credentials in the Python script are correct
  • Test database connection manually using psql

Issue: CGI script shows “Exec format error” or “Internal Server Error”

Symptoms:

  • Browser shows “Internal Server Error” when accessing /cgi-bin/db_app.py
  • Apache error log shows: AH01241: error spawning CGI child: exec of '/var/www/cgi-bin/db_app.py' failed (Exec format error)

Solution:

# Install dos2unix
sudo yum install dos2unix -y

# Fix shebang line
sudo sed -i '1s|^.*|#!/usr/bin/env python3|' /var/www/cgi-bin/db_app.py

# Set correct permissions
sudo chmod 755 /var/www/cgi-bin/db_app.py

# Fix line endings (Windows to Unix)
sudo dos2unix /var/www/cgi-bin/db_app.py

# Restart Apache
sudo systemctl restart httpd

Root cause: This usually happens when the script was created with Windows line endings or has an incorrect shebang line.


Cost Optimization Tips

💰 To minimize costs while working on this project:

  1. Stop (don’t terminate) the EC2 instance when not in use

    • You’ll still pay for EBS storage (~$0.80/month)
    • But save on compute costs (~$8.50/month)
  2. Stop the RDS instance when not actively testing

    • Go to RDS console → Select database → Actions → Stop temporarily
    • Can be stopped for up to 7 days
    • Saves ~$12/month when stopped
  3. Delete resources as soon as you complete the migration

    • Follow the CLEANUP.md guide at the end

Next Steps

Phase 1 Complete!

You now have a fully functional “on-premises” environment with:

  • A web server running Apache with a database-connected application
  • A PostgreSQL database with realistic sample data
  • Proper networking and security configured

Proceed to Phase 2: Build the Target Cloud Environment with VPC peering and a production-grade Multi-AZ RDS instance.


Quick Reference Commands

# SSH to web server
ssh -i on-prem-web-key.pem ec2-user@YOUR_PUBLIC_IP

# Connect to database
psql --host=YOUR_RDS_ENDPOINT --port=5432 --username=postgres --dbname=legacydb

# Check Apache status
sudo systemctl status httpd

# View Apache logs
sudo tail -f /var/log/httpd/access_log
sudo tail -f /var/log/httpd/error_log

# Restart Apache
sudo systemctl restart httpd

# Test database connectivity
psql --host=YOUR_RDS_ENDPOINT --port=5432 --username=postgres --dbname=legacydb -c "SELECT COUNT(*) FROM users;"

Conclusion

In this comprehensive guide, we’ve successfully built a production-grade legacy source environment that perfectly simulates an on-premises infrastructure. This foundation is essential for understanding cloud migration strategies and provides the perfect testing ground for AWS Application Migration Service (MGN) and Database Migration Service (DMS).

What we’ve accomplished:

  • VPC Architecture: Created a properly segmented network with public and private subnets
  • Web Server: Deployed an Apache web server with database connectivity
  • Database: Set up a PostgreSQL RDS instance with realistic sample data
  • Application: Built a database-connected web application using Python CGI
  • Security: Implemented proper security groups and network isolation
  • Documentation: Created a complete resource inventory for future phases

Key Learning Outcomes:

  • Understanding of VPC networking fundamentals
  • Hands-on experience with EC2 and RDS services
  • Database connectivity and application development
  • Security best practices for multi-tier architectures
  • Cost optimization strategies for learning environments

This environment is now ready for the next phase of our migration journey, where we’ll build the target AWS environment and begin the actual migration process using AWS MGN and DMS.


This is Part 1 of a comprehensive AWS migration series. Here is the Part 2, where we’ll build the target cloud environment with Multi-AZ RDS and VPC peering.

Table of Contents