<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muhammed Rifkhan</title>
    <description>The latest articles on DEV Community by Muhammed Rifkhan (@muhammed_rifkhan_9231a67d).</description>
    <link>https://dev.to/muhammed_rifkhan_9231a67d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4007449%2Fc43e73d9-774d-45a8-9069-c1014db1ae5f.png</url>
      <title>DEV Community: Muhammed Rifkhan</title>
      <link>https://dev.to/muhammed_rifkhan_9231a67d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhammed_rifkhan_9231a67d"/>
    <language>en</language>
    <item>
      <title>How I Deployed a Production-Style 3-Tier Application on AWS with CI/CD</title>
      <dc:creator>Muhammed Rifkhan</dc:creator>
      <pubDate>Mon, 29 Jun 2026 07:56:37 +0000</pubDate>
      <link>https://dev.to/muhammed_rifkhan_9231a67d/how-i-deployed-a-production-style-3-tier-application-on-aws-with-cicd-2jdg</link>
      <guid>https://dev.to/muhammed_rifkhan_9231a67d/how-i-deployed-a-production-style-3-tier-application-on-aws-with-cicd-2jdg</guid>
      <description>&lt;p&gt;Recently, I completed a production-style AWS deployment project where I built and deployed a full-stack Application using a proper 3-tier cloud architecture.&lt;/p&gt;

&lt;p&gt;The main goal was not only to make the application work, but also to design it in a way that follows real production concepts such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;HTTPS&lt;/li&gt;
&lt;li&gt;Custom domain&lt;/li&gt;
&lt;li&gt;Public and private subnets&lt;/li&gt;
&lt;li&gt;Load balancing&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;Private database access&lt;/li&gt;
&lt;li&gt;Nginx reverse proxy&lt;/li&gt;
&lt;li&gt;PM2 process management&lt;/li&gt;
&lt;li&gt;GitHub Actions CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/Rifkhan-SAA-DevOps/Blog_3Tier_Arch_AWS" rel="noopener noreferrer"&gt;https://github.com/Rifkhan-SAA-DevOps/Blog_3Tier_Arch_AWS&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Project Overview&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is a full-stack Application with:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User authentication using JWT&lt;br&gt;
Blog create, read, update, and delete features&lt;br&gt;
Category management&lt;br&gt;
Comment system&lt;br&gt;
Admin and author roles&lt;br&gt;
Protected API routes&lt;br&gt;
React + Vite frontend&lt;br&gt;
Node.js + Express backend&lt;br&gt;
MySQL database hosted on Amazon RDS&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application was first deployed manually on AWS using a complete 3-tier architecture. After the manual deployment was completed and verified, I added GitHub Actions CI/CD pipelines to automate both frontend and backend deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Architecture Diagram&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotu2mmeja31hdkvp1knc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotu2mmeja31hdkvp1knc.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The application follows this flow:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User Browser &lt;br&gt;
       ↓ &lt;br&gt;
Route 53 Custom Domain &lt;br&gt;
      ↓ &lt;br&gt;
ACM SSL Certificate&lt;br&gt;
      ↓ &lt;br&gt;
Public Frontend Application Load Balancer&lt;br&gt;
      ↓ &lt;br&gt;
Frontend Target Group&lt;br&gt;
      ↓ &lt;br&gt;
Frontend Auto Scaling Group&lt;br&gt;
      ↓ &lt;br&gt;
EC2 Instances running Nginx&lt;br&gt;
      ↓ &lt;br&gt;
Nginx serves React build and reverse proxies /api&lt;br&gt;
      ↓ &lt;br&gt;
Internal Backend Application Load Balancer&lt;br&gt;
      ↓ &lt;br&gt;
Backend Target Group&lt;br&gt;
      ↓ &lt;br&gt;
Backend Auto Scaling Group &lt;br&gt;
      ↓ &lt;br&gt;
EC2 Instances running Node.js with PM2 &lt;br&gt;
      ↓ &lt;br&gt;
Private RDS MySQL Database&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most important design decision in this project was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The browser should not directly access the private backend.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, the browser calls the public frontend domain, and Nginx forwards /api requests to the internal backend load balancer.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tech Stack&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Axios&lt;/li&gt;
&lt;li&gt;Nginx for production serving&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Express.js&lt;/li&gt;
&lt;li&gt;JWT authentication&lt;/li&gt;
&lt;li&gt;REST API&lt;/li&gt;
&lt;li&gt;PM2 process manager&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;Amazon RDS MySQL&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;AWS Services&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Public subnets&lt;/li&gt;
&lt;li&gt;Private application subnets&lt;/li&gt;
&lt;li&gt;Private database subnets&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;NAT Gateway&lt;/li&gt;
&lt;li&gt;Route Tables&lt;/li&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;Application Load Balancer&lt;/li&gt;
&lt;li&gt;Auto Scaling Group&lt;/li&gt;
&lt;li&gt;Launch Template&lt;/li&gt;
&lt;li&gt;Amazon RDS MySQL&lt;/li&gt;
&lt;li&gt;Route 53&lt;/li&gt;
&lt;li&gt;AWS Certificate Manager&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;DevOps and CI/CD&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;SSH deployment&lt;/li&gt;
&lt;li&gt;SCP&lt;/li&gt;
&lt;li&gt;Nginx reverse proxy&lt;/li&gt;
&lt;li&gt;PM2&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why I Used a 3-Tier Architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In a simple deployment, we can run the frontend, backend, and database on one server. That is useful for learning, but it is not suitable for production-style architecture.&lt;/p&gt;

&lt;p&gt;In this project, I separated the application into three layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Frontend Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend is a React application built using Vite. In production, the React build files are served by Nginx on EC2 instances.&lt;/p&gt;

&lt;p&gt;The frontend layer is public because users need to access the website through the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Backend Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The backend is a Node.js and Express API. It runs on private EC2 instances and is managed using PM2.&lt;/p&gt;

&lt;p&gt;The backend is not directly exposed to the internet. It is accessed through an internal Application Load Balancer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Database Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The database is Amazon RDS MySQL. It is placed inside private database subnets.&lt;/p&gt;

&lt;p&gt;Only the backend security group can connect to the database. This means the database is not publicly accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;AWS Networking Design&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The VPC was designed with multiple subnet layers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VPC&lt;br&gt;
├── Public Subnets&lt;br&gt;
│   ├── Public Frontend ALB&lt;br&gt;
│   └── Frontend EC2 instances&lt;br&gt;
│&lt;br&gt;
├── Private App Subnets&lt;br&gt;
│   ├── Internal Backend ALB&lt;br&gt;
│   └── Backend EC2 instances&lt;br&gt;
│&lt;br&gt;
└── Private DB Subnets&lt;br&gt;
    ├── RDS MySQL&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This structure improved the security of the application because only the required parts were exposed publicly.&lt;/p&gt;

&lt;p&gt;The frontend load balancer receives public HTTPS traffic. The backend load balancer is internal. The RDS database is private.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;HTTPS and Custom Domain&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For the domain setup, I used:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Route 53 for DNS&lt;/li&gt;
&lt;li&gt;AWS Certificate Manager for SSL/TLS certificate&lt;/li&gt;
&lt;li&gt;HTTPS listener on the public frontend Application Load Balancer&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application can be accessed using:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rifkhan.xyz" rel="noopener noreferrer"&gt;https://rifkhan.xyz&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This helped me understand how domain routing and HTTPS are configured in a real AWS environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Nginx Reverse Proxy Setup&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;One of the most important parts of this project was the Nginx reverse proxy.&lt;/p&gt;

&lt;p&gt;The React frontend uses:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VITE_API_URL=/api&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means the browser sends API requests like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rifkhan.xyz/api/auth/login" rel="noopener noreferrer"&gt;https://rifkhan.xyz/api/auth/login&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then Nginx forwards those /api requests to the internal backend ALB.&lt;/p&gt;

&lt;p&gt;Example Nginx concept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location /api/ {
    proxy_pass http://internal-backend-alb-dns/api/;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solved a major production issue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At first, the frontend was trying to call the internal backend ALB directly from the browser. That caused a timeout because the internal ALB is private and cannot be accessed from the public internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The correct solution was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Browser&lt;br&gt;
  → Public domain /api&lt;br&gt;
  → Nginx reverse proxy&lt;br&gt;
  → Internal backend ALB&lt;br&gt;
  → Backend EC2&lt;br&gt;
  → Private RDS&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;CI/CD with GitHub Actions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After completing the manual deployment, I added GitHub Actions to automate deployments.&lt;/p&gt;

&lt;p&gt;I created separate deployment workflows for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Frontend deployment&lt;/li&gt;
&lt;li&gt;Backend deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Frontend CI/CD Flow&lt;/strong&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Developer pushes frontend code&lt;br&gt;
    ↓&lt;br&gt;
GitHub Actions starts&lt;br&gt;
    ↓&lt;br&gt;
Install dependencies&lt;br&gt;
    ↓&lt;br&gt;
Build React app using VITE_API_URL=/api&lt;br&gt;
    ↓&lt;br&gt;
Upload dist files to frontend EC2 using SCP&lt;br&gt;
    ↓&lt;br&gt;
Copy files to /usr/share/nginx/html&lt;br&gt;
    ↓&lt;br&gt;
Reload Nginx&lt;br&gt;
    ↓&lt;br&gt;
Validate frontend health endpoint&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Frontend deployment proof:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftar5hl2s5zbksvwa8rfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftar5hl2s5zbksvwa8rfq.png" alt=" " width="799" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy2x99hdbwv6nsas6qv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy2x99hdbwv6nsas6qv9.png" alt=" " width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7x3rjqt7jpjwd6arx15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7x3rjqt7jpjwd6arx15.png" alt=" " width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff4m24c55axedjk7djzhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff4m24c55axedjk7djzhb.png" alt=" " width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Backend CI/CD Flow&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The backend was more challenging because the backend EC2 instances are inside private subnets.&lt;/p&gt;

&lt;p&gt;GitHub Actions cannot directly SSH into a private EC2 instance. To solve this, I used the frontend EC2 instance as a jump host.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub Actions&lt;br&gt;
    ↓&lt;br&gt;
Frontend EC2 public instance&lt;br&gt;
    ↓&lt;br&gt;
Backend EC2 private instance&lt;br&gt;
Backend deployment flow:&lt;br&gt;
Developer pushes backend code&lt;br&gt;
    ↓&lt;br&gt;
GitHub Actions starts&lt;br&gt;
    ↓&lt;br&gt;
SSH into frontend EC2 jump host&lt;br&gt;
    ↓&lt;br&gt;
SSH into private backend EC2&lt;br&gt;
    ↓&lt;br&gt;
Upload backend source code&lt;br&gt;
    ↓&lt;br&gt;
Install production dependencies&lt;br&gt;
    ↓&lt;br&gt;
Restart backend using PM2&lt;br&gt;
    ↓&lt;br&gt;
Validate backend health endpoint&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Backend deployment proof:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi918dx8ie5sovu24yscy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi918dx8ie5sovu24yscy.png" alt=" " width="799" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsrumij99my7tcxkh4bbg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsrumij99my7tcxkh4bbg.png" alt=" " width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa2jmcdu6qkqgedtu23nk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa2jmcdu6qkqgedtu23nk.png" alt=" " width="799" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4whpl60sbrvgdntk3onk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4whpl60sbrvgdntk3onk.png" alt=" " width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;PM2 for Backend Process Management&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The backend Node.js application was managed using PM2.&lt;/p&gt;

&lt;p&gt;PM2 helped me keep the backend running continuously and restart it after deployment.&lt;/p&gt;

&lt;p&gt;Useful PM2 commands used:&lt;/p&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pm2 start server.js --name blog-backend
pm2 restart blog-backend
pm2 status
pm2 logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;PM2 process proof:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxf9nzb0e3ue8ml7fn8g8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxf9nzb0e3ue8ml7fn8g8.png" alt=" " width="799" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff03n42r79nwl2b1hbgu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff03n42r79nwl2b1hbgu0.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2my1fusjkky9rvv8y8gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2my1fusjkky9rvv8y8gw.png" alt=" " width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbjjo750f4qlh5qu0wcqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbjjo750f4qlh5qu0wcqz.png" alt=" " width="799" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiyn2uxvppvww47j4ib3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqiyn2uxvppvww47j4ib3.png" alt=" " width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Falleulnlgov65hk25nm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Falleulnlgov65hk25nm8.png" alt=" " width="799" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Security Design&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Security was one of the main points of this project.&lt;/p&gt;

&lt;p&gt;Here is how the security design was planned:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Component     =&amp;gt; Security Approach&lt;br&gt;
 Frontend ALB  =&amp;gt; Public HTTPS access&lt;br&gt;
 Frontend EC2  =&amp;gt; Receives traffic through frontend ALB&lt;br&gt;
 Backend ALB   =&amp;gt; Internal only&lt;br&gt;
 Backend EC2   =&amp;gt; Private subnet, no direct public access&lt;br&gt;
 RDS MySQL     =&amp;gt; Private database subnet&lt;br&gt;
 Database access =&amp;gt; Only backend security group allowed&lt;br&gt;
 API access    =&amp;gt; Browser uses /api through Nginx reverse proxy&lt;br&gt;
 SSL           =&amp;gt; ACM certificate with HTTPS&lt;/p&gt;

&lt;p&gt;Secrets   Stored as environment variables, not committed to GitHub&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This helped me understand the importance of layered security in cloud deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Scalability and High Availability&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This project includes production-style scalability components:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Frontend Auto Scaling Group&lt;br&gt;
Backend Auto Scaling Group&lt;br&gt;
Public Application Load Balancer&lt;br&gt;
Internal Application Load Balancer&lt;br&gt;
Multi-subnet architecture&lt;br&gt;
Health checks for frontend and backend target groups&lt;br&gt;
Private application and database layers&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Auto Scaling Groups allow EC2 instances to be replaced or scaled based on configuration. The load balancers distribute traffic to healthy targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Challenges I Faced and Solved&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Browser Could Not Access Internal Backend ALB&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ERR_CONNECTION_TIMED_OUT&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reason:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The React frontend was built using the internal backend ALB DNS. Since the internal backend ALB is private, the browser could not access it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I changed the frontend API URL to:&lt;br&gt;
VITE_API_URL=/api&lt;br&gt;
Then Nginx reverse proxy forwards /api requests to the internal backend ALB.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Nginx Default Server Block Conflict&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nginx had a conflicting default server block.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I removed/commented the default server block and used only the project-specific Nginx configuration file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then I verified the configuration using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; sudo nginx -t
 sudo systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;3. Backend Private Subnet Deployment&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Backend EC2 instances were in private subnets, so GitHub Actions could not SSH directly into them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I used the frontend EC2 instance as a jump host.&lt;/p&gt;

&lt;p&gt;GitHub Actions&lt;br&gt;
    ↓&lt;br&gt;
Frontend EC2 public instance&lt;br&gt;
    ↓&lt;br&gt;
Backend EC2 private instance&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This helped me understand how private server deployment works in real production environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Live application home page:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6g678h6qopmxqwj52h74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6g678h6qopmxqwj52h74.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Register page:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kldidvt19w5ymbn0kbl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kldidvt19w5ymbn0kbl.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ** Dashboard:**
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdrcq3aa5cglozzy14cl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdrcq3aa5cglozzy14cl.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Health Check Endpoints&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Frontend health endpoint:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rifkhan.xyz/health.html" rel="noopener noreferrer"&gt;https://rifkhan.xyz/health.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Backend health endpoint through public domain:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rifkhan.xyz/api/health" rel="noopener noreferrer"&gt;https://rifkhan.xyz/api/health&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Backend local health endpoint:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://localhost:5000/api/health" rel="noopener noreferrer"&gt;http://localhost:5000/api/health&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Health checks were useful for verifying whether the frontend and backend were running correctly after deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What I Learned&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Through this project, I practiced and implemented:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Designing AWS 3-tier architecture&lt;/li&gt;
&lt;li&gt;Deploying frontend and backend on separate layers&lt;/li&gt;
&lt;li&gt;Using public and private subnets correctly&lt;/li&gt;
&lt;li&gt;Configuring Route 53 custom domain&lt;/li&gt;
&lt;li&gt;Enabling HTTPS using ACM&lt;/li&gt;
&lt;li&gt;Creating public and internal Application Load Balancers&lt;/li&gt;
&lt;li&gt;Configuring Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;Hosting React production build with Nginx&lt;/li&gt;
&lt;li&gt;Reverse proxying API requests through Nginx&lt;/li&gt;
&lt;li&gt;Running Node.js backend using PM2&lt;/li&gt;
&lt;li&gt;Connecting backend to private RDS MySQL&lt;/li&gt;
&lt;li&gt;Debugging ALB, Nginx, API, and security group issues&lt;/li&gt;
&lt;li&gt;Creating GitHub Actions CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Deploying to private EC2 through a jump host&lt;/li&gt;
&lt;li&gt;Understanding real-world production deployment flow&lt;/li&gt;
&lt;li&gt;Future Improvements&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/Rifkhan-SAA-DevOps/Blog_3Tier_Arch_AWS" rel="noopener noreferrer"&gt;https://github.com/Rifkhan-SAA-DevOps/Blog_3Tier_Arch_AWS&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://rifkhan.xyz" rel="noopener noreferrer"&gt;https://rifkhan.xyz&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks for reading. Feedback and suggestions are welcome.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>githubactions</category>
      <category>vpc</category>
    </item>
  </channel>
</rss>
