Containerize and Orchestrate: Building a Real-Time(Three-tier) Chat App with Docker and Kubernetes
Hey DevOps Enthusiast! π
Ready to build your own full-stack chat application running in Docker containers and deployed using Kubernetes on your local machine? In this blog, we're going to walk through the process of containerizing a chat app, use Docker for containers, use Docker Compose for managing multiple containers, and finally deploy everything with Kubernetes (Kind) on your local machine.
I will explain everything in such a way that it will be understandable. Let's start!
What Weβll Build:
Weβll be building a real-time chat application that consists of:
Frontend: A React-based chat interface.
Backend: A Node.js server that handles chat messages and user authentication.
Database: MongoDB to store all the chat data and user profiles.
Real-Time Communication: Using Socket.io for instant messaging.
What You'll Need:
Before we begin, let's make sure you have everything set up:
Docker (to containerize the application)
Docker Compose (to run the application using multiple containers)
Kind (Kubernetes in Docker to run the application on a Kubernetes cluster)
Kubectl (Kubernetes CLI tool to control the cluster)
You can install all of the following tools from official documentation or instructions within the README files. The entire project, including the Docker and Kubernetes configurations, is available in the project repository on GitHub.
full-stack_chatApp
βββ backend/ # Backend dir
β βββ Dockerfile # Dockerfile for the backend
β βββ package-lock.json # Lock file for backend dependencies
β βββ package.json # Backend dependencies and scripts
βββ frontend/ # Frontend application (React)
β βββ Dockerfile # Dockerfile for the frontend
β βββ index.html # Main HTML template for the React app
β βββ nginx.conf # Nginx config for frontend
β βββ package-lock.json # Lock file for frontend dependencies
β βββ package.json # Frontend dependencies
βββ k8s/ # Kubernetes manifests for deployment
β βββ README.md # Documentation for Kubernetes deployment
β βββ backend-deployment.yaml # Kubernetes deployment for the backend
β βββ backend-service.yaml # Kubernetes service for the backend
β βββ frontend-deployment.yaml # Kubernetes deployment for the frontend
β βββ frontend-service.yaml # Kubernetes service for the frontend
β βββ jwt-secret.yaml # Kubernetes secret for JWT token
β βββ mongo-pvc.yaml # Persistent volume claim for MongoDB
β βββ mongodb-deployment.yaml # Kubernetes deployment for MongoDB
β βββ mongodb-service.yaml # Kubernetes service for MongoDB
β βββ namespace.yaml # Kubernetes namespace for isolation
βββ package.json # Project dependencies
βββ Jenkinsfile # Jenkins pipeline file
βββ README.md # Project's main documentation
βββ docker-compose.yml # Docker Compose configuration file
This structure will help you understand where each part of the application is located. The backend and frontend directories contain the application code along with the Dockerfile, while k8s contains Kubernetes deployment manifests. The docker-compose.yml file is used to manage all the containers together using Docker Compose.
Step 1: Build the Frontend Docker Image
Let's begin with the Frontend, which is a React app serving the user interface. It interacts with the backend for sending and receiving chat messages
Frontend Dockerfile
Hereβs the Dockerfile
for the frontend, which is responsible for building the app and serving it with Nginx:
# Stage 1: Build the app
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
ENV NODE_ENV=production
RUN npm run build
# Stage 2: Serve with Nginx
FROM nginx:stable-alpine
RUN rm -rf /usr/share/nginx/html/*
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Explanation:
Build Stage:
- We use a Node.js image to install the app dependencies and build the React app for production.
Serve Stage:
- After building the app, weβll use Nginx to serve the dist files.
Once the Dockerfile
is ready, Itβs time to build the frontend Docker image by running the following commands in your terminal:
cd frontend
docker build -t full-stack_frontend .
Step 2: Build the Backend Docker Image
Now, we require the Backend. The Backend is a Node.js server built with Express. This backend will handle all the API requests (such as login, sending messages, etc.) and real-time communication using Socket.io.
Backend Dockerfile
Hereβs the Dockerfile
for the backend:
# Stage 1: Build the backend
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
# Stage 2: Run the backend in production
FROM node:18-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app ./
EXPOSE 5001
USER appuser
CMD ["npm", "start"]
Explanation:
Build Stage:
- We install only the production dependencies (
npm ci --only=production
) and copy the app files into the container.
- We install only the production dependencies (
Production Stage:
- Here we are setting up the container to run the
Node.js server
withExpress
. We are also adding a non-root user (appuser
) for security.
- Here we are setting up the container to run the
To build the backend Docker image, run the following commands:
cd backend
docker build -t full-stack_backend .
Step 3: Pull the MongoDB Docker Image
Our application requires a MongoDB database
to store chat messages
and user data
. Luckily, MongoDB has an official
Docker image, so we do not need to write a Dockerfile
for it.
Pull the MongoDB image using the following command:
docker pull mongo:latest
Once we pull the image, we can run it as a container along with the frontend and backend.
Here's how we can run the Frontend, Backend, and MongoDB containers separately and test
that everything is working fine
:
Step 4: Run the MongoDB Container
Run the following command to start the MongoDB container:
docker run -d \
--name mongo \
-p 27017:27017 \
--env MONGO_INITDB_ROOT_USERNAME=mongoadmin \
--env MONGO_INITDB_ROOT_PASSWORD=secret \
mongo:latest
-d
: Runs the container in detached mode (background).--name mongo
: Names the container asmongo
.-p 27017:27017
: Maps the MongoDB port to your local machine's port 27017.--env
: Sets environment variables for MongoDB to initialize with a root username and password.
To verify MongoDB is running correctly, you can use:
docker ps
MongoDB should now be accessible at localhost:27017
.
Step 5: Create a Docker Network
Now we need to create a Docker Network.
Run the following command to create a docker network
docker network create fullstack-chatapp
Step 6: Run the Backend Container
Now, we need to run the Backend (Node.js with Express) to handle the chat API and real-time communication. The backend container must be linked to the MongoDB container to work properly.
Run the following command to start the backend:
docker run -d \
--name backend \
--network fullstack-chatapp \
-p 5001:5001 \
--env MONGODB_URI=mongodb://mongoadmin:secret@localhost:27017/chatdb?authSource=admin \
--env JWT_SECRET=your_jwt_secret_key --> (experimental secret is provided in the Repo) \
full-stack_backend:latest
--network fullstack-chatapp
: This will enable the backend to connect with MongoDB using thefullstack-chatapp
network.--env MONGODB_URI
: This will specify the MongoDB connection string, including username, password, and database.--env JWT_SECRET
: This will set the JWT secret key used to authenticate users.
To check if the backend is running correctly, use:
docker ps
You should see the backend running at localhost:5001
.
Step 7: Run the Frontend Container
Now that the backend and database are up, we can run the Frontend (React app served by Nginx) to serve the chat interface.
Run the following command to start the frontend:
docker run -d \
--name frontend \
--network fullstack-chatapp \
-p 80:80 \
full-stack_frontend:latest
--network fullstack-chatapp
: Ensures the frontend can connect to the backend via the fullstack-chatapp network.-p 80:80
: Maps the frontend to port 80 on your local machine.
To verify if the frontend is working:
docker ps
The frontend should be running and accessible at http://localhost
.
Step 8: Verify the Application
At this point, all the individual services (Frontend, Backend, MongoDB) are running in separate containers. You can now verify if the chat application is working correctly:
MongoDB:
- MongoDB should be running on
localhost:27017
. You can access it via MongoDB clients like MongoDB Compass or by usingmongo
CLI to connect to it with the root credentials (mongoadmin
/secret
).
- MongoDB should be running on
Backend:
The backend API should be running on
localhost:5001
. To verify the backend, try sending aGET
request to the backend usingcurl
:curl http://localhost:5001/api/status
This should return some information about the API if itβs working correctly.
Frontend:
Visit
http://localhost
in your browser. If everything is set up correctly, you should see the chat application frontend running.Try logging in, sending messages, and verifying real-time communication (using Socket.io). If the frontend connects to the backend successfully, you should see live messages appearing.
Step 9: Shut Down the Containers
Once youβve verified that everything is working, you can stop the containers using:
docker stop frontend backend mongo
docker rm frontend backend mongo
Step 10: Setting Up Docker Compose
Now that we have the Docker images ready for the frontend, backend, and MongoDB, we can use Docker Compose to run all the services together in one command.
Hereβs a docker-compose.yml
file to manage the services:
version: '3.8'
services:
mongodb:
image: mongo
volumes:
- mongo-data:/data/db
environment:
- MONGO_INITDB_ROOT_USERNAME=mongoadmin
- MONGO_INITDB_ROOT_PASSWORD=secret
ports:
- "27017:27017"
networks:
- fullstack-chatapp
backend:
image: backend
container_name: full-stack_backend
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongoadmin:secret@mongodb:27017/dbname?authSource=admin
- JWT_SECRET=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE3MzI2NTMxMDcsImV4cCI6MTc2NDE4OTEwNywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.qE3lxrcV9TM3nujRqJVeCHPd_4WFPO9eVZj8A_kjabI
- PORT=5001
depends_on:
- mongodb
networks:
- fullstack-chatapp
frontend:
image: frontend
container_name: full-stack_frontend
ports:
- "80:80"
depends_on:
- backend
networks:
- fullstack-chatapp
networks:
fullstack-chatapp:
driver: bridge
volumes:
mongo-data:
Explanation:
MongoDB: This is the MongoDB database container; it listens on port
27017
. It leverages environment variables to get the root user and root password in place.Backend: The backend connects to MongoDB on the URI we set in our
MONGODB_URI
environment variable, listening on port5001
.Frontend: The frontend is served on port
80
, and it relies on the backend.
Now, you can run the entire app using Docker Compose:
docker-compose up -d --build
Visit http://localhost
in your browser to see the chat app live!
Step 11: Deploying with Kubernetes (Kind)
Appreciating you for sticking with the blog for this long journey! Now, letβs deploy this application using Kubernetes on your local machine. We'll be using Kind (Kubernetes in Docker) to create a lightweight Kubernetes cluster.
Step 11.1: Install Kind and Kubectl
Before we begin, make sure you have Kind and Kubectl installed. You can find installation instructions on their respective official websites as well in official Repo.
Once installed, you can verify the installation with:
kind version
kubectl version --client
Step 11.2: Create a Kind Cluster
To create a Kubernetes cluster using Kind, run:
kind create cluster --name chat-app
This will set up a local Kubernetes cluster for you to deploy the app.
Step 11.3: Deploy the Application Using Kubernetes Manifests
Now, letβs use the Kubernetes manifests to deploy the app. These manifest files are YAML files that define how Kubernetes should run the app.
Namespace: Organizes resources.
Deployments: Defines the appβs containers.
Services: Exposes the app to the outside world.
Hereβs a breakdown of the Kubernetes steps:
Create the Namespace:
kubectl apply -f namespace.yaml
Deploy MongoDB:
kubectl apply -f mongodb-deployment.yaml kubectl apply -f mongodb-service.yaml kubectl apply -f mongo-pvc.yaml
Deploy JWT Secret (for user authentication):
kubectl apply -f jwt-secret.yaml
Deploy the Backend:
kubectl apply -f backend-deployment.yaml kubectl apply -f backend-service.yaml
Deploy the Frontend:
kubectl apply -f frontend-deployment.yaml kubectl apply -f frontend-service.yaml
Step 11.4: Access the Application
After deploying, you can check the status of the app using:
kubectl get pods -n chat-app
To access the frontend, use port forwarding:
kubectl port-forward service/frontend 8080:80 -n chat-app
Now, visit http://localhost:8080
in your browser to access the chat app.
Step 12: Clean Up the Kubernetes Cluster
When youβre done, you can delete the Kind cluster and clean up the resources:
kind delete cluster --name chat-app
Conclusion
By following this guide, you've learned how to set up Three Tier Application, ensuring your app is easy to manage and scale. Whether youβre just starting or already familiar with Docker and Kubernetes, this process gives you the foundation to build more complex applications.
Keep experimenting, and don't be afraid to tweak things to suit your needs. The more you practice, the more confident youβll become with these powerful tools!
Happy Learning!