In this blog I will guide you on how to create Docker files and K8s manifests files.
Cloning repo
Clone the repo Chat App Repo
git clone https://github.com/mallikharjuna160003/MERN-Chat-App-deployment-AWS.git
Dockerizing APP
Frontend app
Go the MERN-Chat-App-deployment-AWS/frontend
directory.
We
# ------------------- Stage 01 -----------------------
# Use an official Node.js runtime as a parent image
FROM node:20.17.0 AS build
# Set the working directory
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install --legacy-peer-deps
# Copy the rest of the application code
COPY . .
# Build the application with the legacy OpenSSL provider option
RUN NODE_OPTIONS=--openssl-legacy-provider npm run build
# ------------------ Stage 02 --------------------
# Use Nginx to serve the app
FROM nginx:alpine
# Copy the build files to the Nginx server
COPY --from=build /usr/src/app/build /usr/share/nginx/html
# Expose the port Nginx is running on
EXPOSE 3000
# Start Nginx
CMD ["nginx", "-g", "daemon off;"]
This is multi stage docker file.
stage 01:
Building the dependencies and copying the source code from local to docker image work directory /usr/src/app
.
Stage 02 :
Using nginx:alpine
image copy the build directory from stage 01 to the nginx html directory /usr/share/nginx/html
for serving static file.
We are exposing the container on port 3000
. ["nginx", "-g", "daemon off;"] This is the command that will be executed when the container starts. It's telling the container to run Nginx with the -g flag, which allows you to pass configuration directives directly from the command line. The daemon off; directive ensures that Nginx runs in the foreground, which is necessary when running inside a Docker container since Docker expects the main process to run in the foreground. If Nginx runs as a background process (which is its default behavior), the container would immediately exit because there would be no foreground process.
Let's build the docker image
docker build -t a6j0n/frontend:latest .
Now push it to docker hub. Make sure the docker account created. Here a6j0n is the username for dockerhub. We need to prefix the username for the image. It is convention used when pushing images to dockeruhub.
docker push a6j0n/frontend:latest
list the images build in localhost
docker images
Backend App:
Go the backend directory. Here is the Dockerfile. We are using the node:20-slim
image to build the code. Installing the dependencies and copying source code to docker image direcrtory /usr/src/app
. It is exposed on port 5000
. While running the container from the image the CMD
is taking the default arguments as ["node",'server.js']
. These arguments can be overridden by docker argument. Best practice to use it run with the ENTRY_POINT
.
ENTRYPOINT defines the command that cannot be overridden when the container is started, unless you explicitly specify a new ENTRYPOINT at runtime.
CMD defines default arguments to the ENTRYPOINT or provides the command to run if none is specified at runtime. If you pass additional arguments when running the container, they will replace
the CMD values.
# Use the official Node.js image as the base image
FROM node:20-slim
# Install required dependencies for certificates, curl (for healthchecks), and wget
RUN apt-get update && apt-get install -y \
curl \
wget \
&& rm -rf /var/lib/apt/lists/*
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
# COPY package*.json ./
COPY ./package.json ./
COPY ./package-lock.json ./
# Install dependencies
RUN npm install --legacy-peer-deps \
&& npm cache clean --force # Clean npm cache to reduce image size
# Copy the application code
COPY . .
# Expose the application port
EXPOSE 5000
# Start the application
CMD ["node", "server.js"]
Let's build the docker image
docker build -t a6j0n/backend:latest .
Now push it to docker hub.
docker push a6j0n/backend:latest
list the images build in localhost
docker images
Creating K8s Manifest file
Frontend APP:
- We need the default.conf nginx directives file. Create a configmap and pass it as volume mount to the frontend deployment.
server {
listen 80;
listen [::]:80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend-service:5000; # Ensure this is correct
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /socket.io/ {
proxy_pass http://backend-service:5000; # Ensure this points to your backend service
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# CORS headers for WebSocket
add_header 'Access-Control-Allow-Origin' 'http://localhost:3000';
add_header 'Access-Control-Allow-Credentials' 'true';
}
location /app {
proxy_pass http://localhost:3000; # React app should be running here
}
}
store it in the default.conf
file. Create configmap from the file. We are working in the default namespace only for this project.
kubectl create configmap nginx-conf --from-file=default.conf
#listing the configmap
kubectl get cm
- Creating deployment with the above image build and pushed to dockerhub. Note: run docker login in the host for fetching images from dockerhub.
- Mount the configmap as voulume in the deployment.
- Creating Serving of ClusterIP.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
labels:
app: frontend
spec:
replicas: 1 # Define how many frontend pods you want to run
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: a6j0n/frontend:latest # Replace with your actual image name
ports:
- containerPort: 80 # Port for your React app (or 3000, depending on your configuration)
env:
- name: REACT_APP_API_URL
value: "http://backend-service:5000" # Replace with the backend service URL
volumeMounts:
- name: nginx-conf
mountPath: "/etc/nginx/conf.d/default.conf"
subPath: "default.conf"
readOnly: true
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80 # Make sure this matches the port in the frontend container
type: ClusterIP # You can use LoadBalancer or NodePort for external access
Create Backend Deployment and service.
Create the backend deployment and service with the below manifest files. The backend deployment need the mongo database credentails. So, we create a secrets db-creds
. The DB data key is pointing to the base64 encoded strings as values. We are running the backend at port 5000 and service at port 5000.
# db-creds.yaml
apiVersion: v1
data:
MONGO_INITDB_DATABASE: Y2hhdGFwcA==
MONGO_INITDB_ROOT_PASSWORD: ZXhhbXBsZQ==
MONGO_INITDB_ROOT_USERNAME: cm9vdA==
kind: Secret
metadata:
creationTimestamp: null
name: db-creds
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: a6j0n/backend:latest
env:
- name: DOCDB_ENDPOINT
value: "mongo-service"
- name: JWT_SECRET
value: "piyush"
- name: PORT
value: "5000"
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: db-creds
key: MONGO_INITDB_ROOT_USERNAME
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-creds
key: MONGO_INITDB_ROOT_PASSWORD
- name: MONGO_INITDB_DATABASE
valueFrom:
secretKeyRef:
name: db-creds
key: MONGO_INITDB_DATABASE
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 5000
targetPort: 5000
create the deployment and service and secrets from the the files created.
kubectl apply -f backend-deployment.yaml
kubectl apply -f db-creds.yaml
# check the deployments,services,secrets
kubectl get deploy,secrets,svc
Database
The application requires the Mongodb. We are create the mongodb deployment with the mongo official images. Mounted the secrets we created earlier. For the backend pods to communicate with DB we also need the DB service running on port 27017.
DB-Deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo-deployment
spec:
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:5.0
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: db-creds
key: MONGO_INITDB_ROOT_USERNAME
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-creds
key: MONGO_INITDB_ROOT_PASSWORD
- name: MONGO_INITDB_DATABASE
valueFrom:
secretKeyRef:
name: db-creds
key: MONGO_INITDB_DATABASE
ports:
- containerPort: 27017
---
apiVersion: v1
kind: Service
metadata:
name: mongo-service
spec:
selector:
app: mongo
ports:
- protocol: TCP
port: 27017
targetPort: 27017
type: ClusterIP # Headless service for direct communication with pods
Installing Nginx-Ingress controller
This ingress setup need helm
. Try installing from the official docs.
- Add the nginx-ingress controller to helm repo.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
- Update helm repo
helm repo update
- Install the ingress-controller. It will auto create the
ingress-nginx
namespace along with the ingress controller.
helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace
- Check all the components were present. The ingress controller namespace has the ingress controller pod, services like admisition controller,ingress controller service, deployment, replicaset.
kubectl get all -n ingress-nginx
- Create Ingress resource. The ingress resource can be created in the namespace we want to the services to expose. Here I am running on localhost so host is
localhost
. The frontend will be serving at/
prefix route and backend at/api/
route. The ports are frontend service port 80 and backend service port 5000. The ingress will match the requests in the the order we mentioned routes order.
# ingress-resource.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
namespace: default
annotations:
#nginx.ingress.kubernetes.io/rewrite-target: /
ingress.class: "nginx"
spec:
ingressClassName: nginx # Add this line to specify the ingress class
rules:
- host: localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
- path: /api/
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 5000
Try running the ingress service by portforwarding from 80 to 8080 on localhost.
kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 8080:8080
Finally we have the application running on localhost.
Top comments (0)