DEV Community

Cover image for ThreeTierApplication: A Modern DevOps Todo Deployment using DevSecOps on AWS EKS
Deepanshu
Deepanshu

Posted on • Edited on

ThreeTierApplication: A Modern DevOps Todo Deployment using DevSecOps on AWS EKS

Welcome to the ultimate hands-on guide for deploying a production-ready three-tier web application! This comprehensive tutorial will walk you through the ThreeTierAppChallenge - a modern todo application built with React, Node.js, and MongoDB, complete with Docker containerization, Kubernetes manifests, and CI/CD pipelines. Whether you're looking to learn modern DevOps practices or deploy a scalable web application, this guide has everything you need to get started and master the deployment pipeline.


The ThreeTierAppChallenge is a full-stack todo application featuring a React frontend, Node.js backend, and MongoDB database, designed for cloud-native deployment on AWS EKS. Clone the repo, run with Docker Compose in under 5 minutes, or dive deep into Kubernetes deployment with included manifests and Jenkins CI/CD pipelines. Perfect for learning modern DevOps practices including containerization, orchestration, and infrastructure as code.


Prerequisites

Before diving in, make sure you have these tools installed:

  • Git (for version control)
  • Node.js (v18+ recommended) and npm
  • Docker and Docker Compose (for containerization)
  • Optional but recommended:

πŸš€ Quick Local Setup

Let's get the application running on your local machine in just a few minutes!

Step 1: Clone the Repository

git clone https://github.com/YOUR_USERNAME/TWSThreeTierAppChallenge.git
cd TWSThreeTierAppChallenge
Enter fullscreen mode Exit fullscreen mode

Step 2: Run with Docker Compose (Fastest Method)

The easiest way to get everything running is with Docker Compose:

docker-compose up --build
Enter fullscreen mode Exit fullscreen mode

Sample Output:

[+] Building 45.2s (23/23) FINISHED
[+] Running 3/3
 βœ” Container mongodb    Started
 βœ” Container backend    Started  
 βœ” Container frontend   Started
Enter fullscreen mode Exit fullscreen mode

After a few minutes, you'll have:

Step 3: Manual Setup (Development Mode)

For development, you might want to run services individually:

Backend Setup:

cd Application-Code/backend
npm install
npm start
Enter fullscreen mode Exit fullscreen mode

Frontend Setup (in a new terminal):

cd Application-Code/frontend
npm install
npm start
Enter fullscreen mode Exit fullscreen mode

Sample Output:

Compiled successfully!

You can now view client in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.1.100:3000

webpack compiled with 0 errors
Enter fullscreen mode Exit fullscreen mode

Step 4: Run Tests

# Backend tests
cd Application-Code/backend
npm test

# Frontend tests  
cd Application-Code/frontend
npm test
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Annotated Dockerfile Analysis

Let's examine the frontend Dockerfile to understand the containerization strategy:

FROM node:18-alpine AS build        # 1. Multi-stage build for optimization
WORKDIR /app
COPY package*.json ./               # 2. Copy package files first for layer caching
RUN npm install                     # 3. Install dependencies
COPY . .                            # 4. Copy application source code
RUN npm run build                   # 5. Build the React production bundle

FROM nginx:alpine                   # 6. Use lightweight Nginx for serving
COPY --from=build /app/build /usr/share/nginx/html  # 7. Copy built assets
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]  # 8. Start Nginx in foreground
Enter fullscreen mode Exit fullscreen mode

Key Benefits:

  • Multi-stage build reduces final image size by ~70%
  • Layer caching speeds up subsequent builds
  • Nginx provides production-grade static file serving
  • Alpine base minimizes security attack surface

πŸ—οΈ Architecture Overview

Diagram Description:
Create a diagram showing three tiers connected vertically:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Frontend      β”‚ ← React.js (Port 3000)
β”‚   (React)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚ HTTP/REST API
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚   Backend       β”‚ ← Node.js/Express (Port 3500)  
β”‚   (Node.js)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚ MongoDB Connection
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚   Database      β”‚ ← MongoDB (Port 27017)
β”‚   (MongoDB)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Additional Infrastructure:

  • Docker Containers around each tier
  • Kubernetes Pods for orchestration
  • AWS EKS for managed Kubernetes
  • Jenkins for CI/CD automation

πŸ’‘ Three Essential Code Snippets

1. Smart Database Connection with Fallback

// Application-Code/backend/db.js
module.exports = async () => {
    // Skip MongoDB connection if not using MongoDB
    if (process.env.USE_MONGODB !== 'true') {
        console.log("Using file-based storage (MongoDB disabled)");
        return;
    }

    try {
        const connectionParams = {
            useNewUrlParser: true,
            useUnifiedTopology: true,
        };
        const useDBAuth = process.env.USE_DB_AUTH || false;
        if(useDBAuth){
            connectionParams.user = process.env.MONGO_USERNAME;
            connectionParams.pass = process.env.MONGO_PASSWORD;
        }

        await mongoose.connect(process.env.MONGO_CONN_STR, connectionParams);
        console.log("Connected to MongoDB database.");
    } catch (error) {
        console.log("Could not connect to database. Falling back to file-based storage...");
        process.env.USE_MONGODB = 'false';
    }
};
Enter fullscreen mode Exit fullscreen mode

Line-by-line breakdown:

  • Lines 3-6: Check environment variable to enable/disable MongoDB
  • Lines 8-16: Configure connection parameters with optional authentication
  • Lines 18: Attempt MongoDB connection using environment variables
  • Lines 21-23: Graceful fallback to file-based storage on connection failure

This pattern ensures your application works in any environment!


2. Frontend API Service with Error Handling

// Application-Code/frontend/src/services/taskServices.js
export const taskService = {
    getTasks: async (filters = {}) => {
        const params = new URLSearchParams();
        Object.entries(filters).forEach(([key, value]) => {
            if (value !== undefined && value !== null && value !== '') {
                params.append(key, value);
            }
        });

        const response = await axios.get(`${apiUrl}?${params}`);
        return response.data;
    },

    addTask: async (taskData) => {
        try {
            const response = await axios.post(apiUrl, taskData);
            return response.data;
        } catch (error) {
            console.error('Add task error:', error);
            throw error;
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

What's happening:

  • Lines 3-9: Dynamic query parameter building for flexible filtering
  • Lines 11-12: Clean GET request with constructed parameters
  • Lines 15-22: POST request with comprehensive error handling
  • Lines 19-20: Proper error logging and re-throwing for upstream handling

3. Production-Ready Kubernetes Deployment

# Kubernetes-Manifests-file/Backend/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata: 
  name: api
  namespace: three-tier
spec: 
  replicas: 2                        # High availability with 2 instances
  strategy: 
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                    # Allow 1 extra pod during updates
      maxUnavailable: 25%            # Maximum 25% downtime during updates
  template:
    spec:
      containers:
      - name: api
        env:
          - name: MONGO_CONN_STR
            value: mongodb://mongodb-svc:27017/todo?directConnection=true
        ports:
        - containerPort: 3500
        livenessProbe:               # Health check configuration
          httpGet:
            path: /ok
            port: 3500
          initialDelaySeconds: 2
          periodSeconds: 5
Enter fullscreen mode Exit fullscreen mode

Deployment highlights:

  • Line 8: Two replicas ensure high availability
  • Lines 10-13: Rolling updates with zero downtime
  • Lines 18-19: Service discovery using Kubernetes DNS
  • Lines 22-27: Health checks for automatic recovery

🚨 Common Issues & Solutions

Issue 1: Backend Using File Storage Instead of MongoDB

Symptoms: API works but data doesn't persist between container restarts

Solution:

# Check backend logs
docker logs backend

# Set environment variable
echo "USE_MONGODB=true" >> Application-Code/backend/.env
echo "MONGO_CONN_STR=mongodb://mongodb:27017/todo" >> Application-Code/backend/.env

# Restart containers
docker-compose restart backend
Enter fullscreen mode Exit fullscreen mode

Issue 2: Frontend Can't Connect to Backend

Symptoms: Frontend loads but no tasks appear, network errors in browser console

Solution:

# Check if backend is running
curl http://localhost:3500/health

# Verify environment variable
echo "REACT_APP_BACKEND_URL=http://localhost:3500/api/tasks" >> Application-Code/frontend/.env

# Rebuild frontend
docker-compose build frontend
Enter fullscreen mode Exit fullscreen mode

Issue 3: Port Conflicts

Symptoms: docker-compose up fails with "port already in use"

Solution:

# Check what's using the ports
lsof -i :3000,3500,27017

# Kill conflicting processes or change ports in docker-compose.yml
# Example: Change frontend port to 3001
ports:
  - "3001:3000"
Enter fullscreen mode Exit fullscreen mode

πŸ”„ CI/CD Pipeline Overview

The project includes comprehensive Jenkins pipelines for both frontend and backend:

Pipeline Features:

  • Code Quality: SonarQube analysis
  • Security: OWASP dependency checks and Trivy scanning
  • Containerization: Docker image building and ECR pushing
  • GitOps: Automatic deployment manifest updates

To set up CI/CD:

  1. Deploy Jenkins using the provided Terraform scripts
  2. Configure the provided Jenkinsfile pipelines
  3. Set up AWS ECR repositories for image storage
  4. Watch automated deployments to EKS!

☸️ Kubernetes Deployment

Ready to go production? Deploy to Kubernetes:

# Create namespace
kubectl create namespace three-tier

# Deploy database
kubectl apply -f Kubernetes-Manifests-file/Database/

# Create MongoDB credentials
kubectl create secret generic mongo-sec \
  --from-literal=username=admin \
  --from-literal=password=password123 \
  -n three-tier

# Deploy backend and frontend
kubectl apply -f Kubernetes-Manifests-file/Backend/
kubectl apply -f Kubernetes-Manifests-file/Frontend/

# Set up ingress (requires AWS Load Balancer Controller)
kubectl apply -f Kubernetes-Manifests-file/ingress.yaml

# Check deployment status
kubectl get all -n three-tier
Enter fullscreen mode Exit fullscreen mode

πŸš€ What's Next?

Congratulations! You've successfully deployed a modern three-tier application. Here are your next steps:

🎯 Immediate Next Steps:

  • Explore the Code: Dive deeper into the Application-Code directory
  • Try Kubernetes: Use the manifests in Kubernetes-Manifests-file
  • Set Up CI/CD: Implement the Jenkins pipelines from Jenkins-Pipeline-Code

πŸ“š Learning Opportunities:

  • Infrastructure as Code: Study the Terraform scripts in Jenkins-Server-TF
  • Security: Implement the security best practices shown in the code
  • Monitoring: Add Prometheus and Grafana (hint: it's in the challenge levels!)

🀝 Community & Contributing:

  • Report Issues: Found a bug? Open an issue
  • Contribute: Check the contribution guidelines
  • Share Your Experience: Write about your deployment journey!

πŸ† Challenge Yourself:

  • Bronze: Get the basic deployment working βœ…
  • Silver: Add SSL/TLS and monitoring
  • Gold: Implement GitOps with ArgoCD
  • Platinum: Add your own innovative features!

πŸ“‹ Quick Reference Commands

# Development
npm start                          # Start local development
docker-compose up                  # Run full stack locally
kubectl get pods -n three-tier     # Check Kubernetes status

# Troubleshooting  
docker logs container_name         # Check container logs
kubectl describe pod pod_name      # Debug Kubernetes issues
curl localhost:3500/health         # Test backend health

# Cleanup
docker-compose down               # Stop local environment
kubectl delete namespace three-tier  # Remove Kubernetes deployment
Enter fullscreen mode Exit fullscreen mode

Recommended dev.to tags: tutorial, javascript, react, nodejs, docker, kubernetes, devops, aws, ci-cd, mongodb


Ready to build something amazing? Clone the repo and start your DevOps journey today! πŸš€

Have questions or run into issues? Drop a comment below or reach out to the community. Happy coding!

Top comments (0)