A production-ready DevOps project featuring a containerized Node.js/TypeScript backend API with PostgreSQL, a React/TypeScript frontend served by Nginx, automated CI/CD pipelines, Kubernetes manifests, and Terraform infrastructure-as-code for AWS.
┌──────────────────────────────────────────────┐
│ GitHub Actions CI/CD │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ CI: Build │ │ CD: Deploy│ │ Pages: │ │
│ │ & Push │──│ to EC2 │ │ React App │ │
│ └──────────┘ └──────────┘ └───────────┘ │
└──────────────────────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────────────────────────┐ ┌──────────────────┐
│ AWS Infrastructure │ │ GitHub Pages │
│ │ │ (React Frontend)│
│ ┌─────────────────────────────────────────┐ │ └──────────────────┘
│ │ EKS Cluster (K8s 1.29) │ │
│ │ ┌─────────────┐ ┌────────────────┐ │ │
│ │ │ API Server │ │ PostgreSQL │ │ │
│ │ │ (Node.js) │──▶│ (StatefulSet) │ │ │
│ │ │ Port: 3000 │ │ Port: 5432 │ │ │
│ │ └─────────────┘ └────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ EC2 Instance (Docker Compose) │ │
│ │ API + PostgreSQL via docker-compose │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
| Layer | Technology | Details |
|---|---|---|
| Frontend | React + TypeScript | Multi-stage Docker build, Nginx |
| Backend | Node.js + TypeScript | Multi-stage Docker build, port 3000 |
| Database | PostgreSQL | StatefulSet (K8s) / Docker service |
| Web Server | Nginx | Serves React SPA, port 80 |
| Containers | Docker | Optimized multi-stage builds |
| Orchestration | Kubernetes (EKS) | v1.29, auto-scaling 1-3 nodes |
| IaC | Terraform | AWS VPC, EKS, EC2 |
| CI/CD | GitHub Actions | Docker build/push, EC2 deploy, GitHub Pages |
| Cloud | AWS | ap-south-1 (Mumbai) |
devops/
├── terraform/
│ ├── main.tf # EC2 instance + security group
│ ├── variables.tf # EC2 variables (region, instance type)
│ └── kubernetes/
│ ├── main.tf # EKS cluster + VPC (3 AZs)
│ ├── provider.tf # AWS provider configuration
│ └── variable.tf # EKS variables
├── nodejs-server/
│ ├── Dockerfile # Optimized multi-stage build
│ ├── Dockerfile_unoptimized # Simple single-stage build
│ ├── docker-compose.yml # Local development environment
│ ├── docker-compose.prod.yml # Production deployment
│ ├── .github/workflows/
│ │ ├── ci.yml # Build & push Docker image
│ │ └── cd.yml # Deploy to EC2 via SSH
│ └── kubernetes/
│ ├── api-deployment.yaml # API deployment with health probes
│ ├── api-service.yaml # API ClusterIP service
│ ├── postgres-config.yaml # Database ConfigMap
│ ├── postgres-secret.yaml # Database credentials Secret
│ ├── postgres-service.yaml # Database ClusterIP service
│ └── postgres-statefulset.yaml # PostgreSQL StatefulSet (1Gi PVC)
└── react-app/
├── Dockerfile # Multi-stage build (Node → Nginx)
├── nginx.conf # SPA routing configuration
└── .github/workflows/
└── deploy.yml # Deploy to GitHub Pages
-
Navigate to the backend directory:
cd nodejs-server -
Create a
.envfile with database credentials:POSTGRES_USER=myuser POSTGRES_PASSWORD=mypassword POSTGRES_DB=mydb
-
Start the services:
docker-compose up --build
This starts the Node.js API on port 3000 and PostgreSQL on port 5432.
Uses the pre-built image from Docker Hub (scylla23/health-server:latest):
cd nodejs-server
# Create .env file with production credentials
docker-compose -f docker-compose.prod.yml up -dProvision the EKS cluster with Terraform:
cd terraform/kubernetes
terraform init
terraform plan
terraform applyThis creates:
- VPC with CIDR
10.0.0.0/16across 3 availability zones - 3 private subnets (
10.0.1.0/24,10.0.2.0/24,10.0.3.0/24) for EKS nodes - 3 public subnets (
10.0.101.0/24,10.0.102.0/24,10.0.103.0/24) - NAT gateway for private subnet internet access
- EKS cluster (K8s 1.29) with managed node group (2 desired, scales 1-3)
Deploy the application:
# Configure kubectl for the EKS cluster
aws eks update-kubeconfig --name learning-cluster --region ap-south-1
# Apply Kubernetes manifests
kubectl apply -f nodejs-server/kubernetes/postgres-config.yaml
kubectl apply -f nodejs-server/kubernetes/postgres-secret.yaml
kubectl apply -f nodejs-server/kubernetes/postgres-statefulset.yaml
kubectl apply -f nodejs-server/kubernetes/postgres-service.yaml
kubectl apply -f nodejs-server/kubernetes/api-deployment.yaml
kubectl apply -f nodejs-server/kubernetes/api-service.yamlProvision an EC2 instance for Docker Compose deployment:
cd terraform
terraform init
terraform plan
terraform applyThis creates a t2.micro EC2 instance in ap-south-1 with a security group allowing:
- SSH access on port 22
- Application traffic on port 3000
The CI/CD pipeline automatically deploys to this instance on push to main.
The React frontend is automatically deployed to GitHub Pages on every push to the main branch via the workflow at react-app/.github/workflows/deploy.yml.
Triggered on push to main:
- Sets up Docker Buildx
- Logs in to Docker Hub
- Builds and pushes the image with tags:
scylla23/health-server:latestscylla23/health-server:<commit-sha>
Triggered when the CI workflow completes:
- Copies
docker-compose.prod.ymlto EC2 via SCP - SSHs into EC2 and creates the
.envfile - Pulls the latest Docker image
- Runs
docker-compose -f docker-compose.prod.yml up -d
Triggered on push to main:
- Builds the React app with Node.js v23
- Uploads the
distfolder as a GitHub Pages artifact - Deploys to GitHub Pages
| Secret | Used In | Description |
|---|---|---|
DOCKERHUB_USERNAME |
CI | Docker Hub username |
DOCKERHUB_TOKEN |
CI | Docker Hub access token |
EC2_HOST |
CD | EC2 instance public IP or hostname |
EC2_SSH_KEY |
CD | Private SSH key for EC2 access |
DOCKER_ENV_FILE |
CD | Contents of the .env file for production |
The API deployment includes Kubernetes health probes on the /api/health endpoint:
| Probe | Initial Delay | Interval | Purpose |
|---|---|---|---|
| Readiness | 5s | 10s | Ensures pod is ready to receive traffic |
| Liveness | 20s | 60s | Restarts pod if it becomes unresponsive |