DEV Community

Muhammed Rifkhan
Muhammed Rifkhan

Posted on

How I Deployed a Production-Style 3-Tier Application on AWS with CI/CD

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.

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:

  • HTTPS
  • Custom domain
  • Public and private subnets
  • Load balancing
  • Auto Scaling Groups
  • Private database access
  • Nginx reverse proxy
  • PM2 process management
  • GitHub Actions CI/CD

GitHub Repository:

https://github.com/Rifkhan-SAA-DevOps/Blog_3Tier_Arch_AWS

Project Overview

This is a full-stack Application with:

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

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.

Architecture Diagram

The application follows this flow:

User Browser

Route 53 Custom Domain

ACM SSL Certificate

Public Frontend Application Load Balancer

Frontend Target Group

Frontend Auto Scaling Group

EC2 Instances running Nginx

Nginx serves React build and reverse proxies /api

Internal Backend Application Load Balancer

Backend Target Group

Backend Auto Scaling Group

EC2 Instances running Node.js with PM2

Private RDS MySQL Database

The most important design decision in this project was this:

The browser should not directly access the private backend.

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

Tech Stack

Frontend

  • React
  • Vite
  • JavaScript
  • Axios
  • Nginx for production serving

Backend

  • Node.js
  • Express.js
  • JWT authentication
  • REST API
  • PM2 process manager

Database

  • MySQL
  • Amazon RDS MySQL

AWS Services

  • VPC
  • Public subnets
  • Private application subnets
  • Private database subnets
  • Internet Gateway
  • NAT Gateway
  • Route Tables
  • EC2
  • Application Load Balancer
  • Auto Scaling Group
  • Launch Template
  • Amazon RDS MySQL
  • Route 53
  • AWS Certificate Manager
  • Security Groups

DevOps and CI/CD

  • Git
  • GitHub
  • GitHub Actions
  • SSH deployment
  • SCP
  • Nginx reverse proxy
  • PM2
  • Linux

Why I Used a 3-Tier Architecture

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.

In this project, I separated the application into three layers:

1. Frontend Layer

The frontend is a React application built using Vite. In production, the React build files are served by Nginx on EC2 instances.

The frontend layer is public because users need to access the website through the browser.

2. Backend Layer

The backend is a Node.js and Express API. It runs on private EC2 instances and is managed using PM2.

The backend is not directly exposed to the internet. It is accessed through an internal Application Load Balancer.

3. Database Layer

The database is Amazon RDS MySQL. It is placed inside private database subnets.

Only the backend security group can connect to the database. This means the database is not publicly accessible.

AWS Networking Design

The VPC was designed with multiple subnet layers:

VPC
├── Public Subnets
│ ├── Public Frontend ALB
│ └── Frontend EC2 instances

├── Private App Subnets
│ ├── Internal Backend ALB
│ └── Backend EC2 instances

└── Private DB Subnets
├── RDS MySQL

This structure improved the security of the application because only the required parts were exposed publicly.

The frontend load balancer receives public HTTPS traffic. The backend load balancer is internal. The RDS database is private.

HTTPS and Custom Domain

For the domain setup, I used:

  • Route 53 for DNS
  • AWS Certificate Manager for SSL/TLS certificate
  • HTTPS listener on the public frontend Application Load Balancer

The application can be accessed using:

https://rifkhan.xyz

This helped me understand how domain routing and HTTPS are configured in a real AWS environment.

Nginx Reverse Proxy Setup

One of the most important parts of this project was the Nginx reverse proxy.

The React frontend uses:

VITE_API_URL=/api

This means the browser sends API requests like this:

https://rifkhan.xyz/api/auth/login

Then Nginx forwards those /api requests to the internal backend ALB.

Example Nginx concept:

location /api/ {
    proxy_pass http://internal-backend-alb-dns/api/;
}
Enter fullscreen mode Exit fullscreen mode

This solved a major production issue.

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.

The correct solution was:

Browser
→ Public domain /api
→ Nginx reverse proxy
→ Internal backend ALB
→ Backend EC2
→ Private RDS

CI/CD with GitHub Actions

After completing the manual deployment, I added GitHub Actions to automate deployments.

I created separate deployment workflows for:

  • Frontend deployment
  • Backend deployment

Frontend CI/CD Flow

Developer pushes frontend code

GitHub Actions starts

Install dependencies

Build React app using VITE_API_URL=/api

Upload dist files to frontend EC2 using SCP

Copy files to /usr/share/nginx/html

Reload Nginx

Validate frontend health endpoint

Frontend deployment proof:

Backend CI/CD Flow

The backend was more challenging because the backend EC2 instances are inside private subnets.

GitHub Actions cannot directly SSH into a private EC2 instance. To solve this, I used the frontend EC2 instance as a jump host.

GitHub Actions

Frontend EC2 public instance

Backend EC2 private instance
Backend deployment flow:
Developer pushes backend code

GitHub Actions starts

SSH into frontend EC2 jump host

SSH into private backend EC2

Upload backend source code

Install production dependencies

Restart backend using PM2

Validate backend health endpoint

Backend deployment proof:

PM2 for Backend Process Management

The backend Node.js application was managed using PM2.

PM2 helped me keep the backend running continuously and restart it after deployment.

Useful PM2 commands used:

pm2 start server.js --name blog-backend
pm2 restart blog-backend
pm2 status
pm2 logs

PM2 process proof:

Security Design

Security was one of the main points of this project.

Here is how the security design was planned:

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

Secrets Stored as environment variables, not committed to GitHub

This helped me understand the importance of layered security in cloud deployment.

Scalability and High Availability

This project includes production-style scalability components:

Frontend Auto Scaling Group
Backend Auto Scaling Group
Public Application Load Balancer
Internal Application Load Balancer
Multi-subnet architecture
Health checks for frontend and backend target groups
Private application and database layers

The Auto Scaling Groups allow EC2 instances to be replaced or scaled based on configuration. The load balancers distribute traffic to healthy targets.

Challenges I Faced and Solved

1. Browser Could Not Access Internal Backend ALB

Problem:

ERR_CONNECTION_TIMED_OUT

Reason:

The React frontend was built using the internal backend ALB DNS. Since the internal backend ALB is private, the browser could not access it.

Solution:

I changed the frontend API URL to:
VITE_API_URL=/api
Then Nginx reverse proxy forwards /api requests to the internal backend ALB.

2. Nginx Default Server Block Conflict

Problem:

Nginx had a conflicting default server block.

Solution:

I removed/commented the default server block and used only the project-specific Nginx configuration file.

Then I verified the configuration using:

 sudo nginx -t
 sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

3. Backend Private Subnet Deployment

Problem:

Backend EC2 instances were in private subnets, so GitHub Actions could not SSH directly into them.

Solution:

I used the frontend EC2 instance as a jump host.

GitHub Actions

Frontend EC2 public instance

Backend EC2 private instance

This helped me understand how private server deployment works in real production environments.

Live application home page:

Register page:

** Dashboard:**

Health Check Endpoints

Frontend health endpoint:

https://rifkhan.xyz/health.html

Backend health endpoint through public domain:

https://rifkhan.xyz/api/health

Backend local health endpoint:

http://localhost:5000/api/health

Health checks were useful for verifying whether the frontend and backend were running correctly after deployment.

What I Learned

Through this project, I practiced and implemented:

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

GitHub Repository:

https://github.com/Rifkhan-SAA-DevOps/Blog_3Tier_Arch_AWS

Live Demo:

https://rifkhan.xyz

Thanks for reading. Feedback and suggestions are welcome.

Top comments (0)