Introduction

In the second part of our guide on How to prepare a NodeJS application for Kubernetes, we’ll dive into creating the Dockerfile and deployment yaml files needed for a smooth Kubernetes deployment. This section will guide you through writing the Dockerfile, crafting the necessary yaml configurations, and ensuring your NodeJS application is fully ready to run in a Kubernetes environment.

Procedure

In the following procedure we’ll see how to create Dockerfiles and yamls for our application.

Dockerfile files

We will need to create two Dockerfile files for our two sides of the application. One is the React side and the other is for NodeJS

Backend Dockerfile

Let’s create the backend NodeJS Dockerfile:

# Use the official Node.js image as the base image
FROM node:20-bullseye

# Set the working directory
WORKDIR /app

# Copy package.json and package-lock.json files to the working directory
COPY package.json package-lock.json ./

# Install the dependencies
RUN npm install

# Copy the rest of the source code
COPY . .

# Expose the API port
EXPOSE 3000

# Start the backend server
CMD ["npm", "run", "start:backend"]

This will have a container with NodeJS server running in it.

Let’s use the file to build the image

$ cd ./k8s-worldcup/backend # this is important to copy the files for the application to the container.
$ docker build -f Dockerfile -t registry.apps.example.k8s.co.il/worldcup/worldcup-backend:1.0 .

Frontend Dockerfile

Let’s prepare the .dockerignore file as well so we won’t copy unneeded folders:

node_modules
backend

Let’s create the frontend NodeJS Dockerfile:

# Use the official Node.js image as the base image
FROM node:20-bullseye

# Set the working directory
WORKDIR /app

# Copy package.json and package-lock.json files to the working directory
COPY package.json package-lock.json ./

# Install the dependencies
RUN npm install

# Copy the rest of the source code
COPY src ./src
COPY public ./public

# Build the frontend React app
RUN npm run build

# Expose the default node port
EXPOSE 3000

# Start the backend server
CMD ["npm", "run", "start:k8s"]

This will have a container with NodeJS server running in it. Same as in the backend, but exposing port 80 intead.

Let’s use the file to build the image

$ cd ../worldcup-frontend
$ docker build -f Dockerfile -t registry.apps.example.k8s.co.il/worldcup/worldcup-frontend:1.0 .

NOTE: If you are running on Mac OSX, please use the following docker build command:

$ docker buildx build --platform linux/amd64 -f Dockerfile -t registry.apps.example.k8s.co.il/worldcup/worldcup-backend:0.1 .

Pushing the image to our registry

In order for the Kubernetes environment to be able to retrieve the images, we will need a registry running. You can use our How to Deploy Harbor Registry on Kubernetes . In order to host these images.

Login to the registry:

$ docker login registry.apps.example.k8s.co.il
Username: harbor-admin
Password: 
Login Succeeded

Let’s push our images to our registry:

# backend
$ docker push registry.apps.example.k8s.co.il/worldcup/worldcup-backend:1.0
# frontend
$ docker push registry.apps.example.k8s.co.il/worldcup/worldcup-frontend:1.0

We have now the images prepared for the application to be running on Kubernetes.

Deployment files

Now we can deploy our application on Kubernetes. For this we will create a namespace, deployment, service and ingress.

Namespace

Let’s create a namespace:

apiVersion: v1
kind: Namespace
metadata:
  labels:
    kubernetes.io/metadata.name: worldcup
    name: worldcup
  name: worldcup
spec:

You can check it with k get namespaces command.

NOTE: In case of a private registry with user/pass to access it, you need to create a secret.yaml for the credentials for retrieving the images. This is out of the scope of this document. We assume the image is publicly available.

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: worldcup
  name: worldcup
  namespace: worldcup
spec:
  replicas: 1
  selector:
    matchLabels:
      app: worldcup
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: worldcup
    spec:
      containers:
      - image: registry.apps.example.k8s.co.il/worldcup/worldcup-frontend:0.1
        imagePullPolicy: IfNotPresent
        name: worldcup
        ports:
        - containerPort: 3000
          protocol: TCP
        env:
        - name: REACT_APP_API_URL
          value: "worldcup-backend.apps.example.k8s.co.il"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: worldcup-backend
  name: worldcup-backend
  namespace: worldcup
spec:
  replicas: 1
  selector:
    matchLabels:
      app: worldcup-backend
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: worldcup-backend
    spec:
      containers:
      - image: registry.apps.example.k8s.co.il/worldcup/worldcup-backend:0.1
        imagePullPolicy: IfNotPresent
        name: worldcup-backend
        ports:
        - containerPort: 3000
          protocol: TCP

Let’s check the pods are runnning with k get pods -n worldcup:

$ k get pods -n worldcup
NAME                                READY   STATUS    RESTARTS   AGE
worldcup-54fb4bb855-t9npz           1/1     Running   0          13m
worldcup-backend-5b754b66d5-76ng7   1/1     Running   0          13m

Service

We shall now deploy our Service yaml:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: worldcup
  name: worldcup-service
  namespace: worldcup
spec:
  ports:
  - port: 443
    protocol: TCP
    targetPort: 3000
  selector:
    app: worldcup
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: worldcup-backend
  name: worldcup-backend
  namespace: worldcup
spec:
  ports:
  - port: 443
    protocol: TCP
    targetPort: 3000
  selector:
    app: worldcup-backend
  type: ClusterIP

We can see the service was created with k get service :

$ k get service
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
worldcup-backend   ClusterIP   10.101.33.155   <none>        443/TCP   1s
worldcup-service   ClusterIP   10.101.36.59    <none>        443/TCP   3s

Ingress

Let’s deploy our ingress. With a certificate as well

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: ca-issuer
    kubernetes.io/ingress.class: nginx
  name: worldcup-ingress
  namespace: worldcup
spec:
  rules:
  - host: worldcup.apps.example.k8s.co.il
    http:
      paths:
      - backend:
          service:
            name: worldcup-service
            port:
              number: 443
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - worldcup.apps.example.k8s.co.il
    secretName: worldcup-tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: ca-issuer
    kubernetes.io/ingress.class: nginx
  name: worldcup-ingress-backend
  namespace: worldcup
spec:
  rules:
  - host: worldcup-backend.apps.example.k8s.co.il
    http:
      paths:
      - backend:
          service:
            name: worldcup-backend
            port:
              number: 443
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - worldcup-backend.apps.example.k8s.co.il
    secretName: worldcup-backend-tls

Let’s check our ingress was created with k get ingress -n worldcup

$ k get ingress -n worldcup

NAME                CLASS    HOSTS                                      ADDRESS        PORTS     AGE
worldcup-ingress    <none>   worldcup.apps.example.k8s.co.il           x.x.x.x   80, 443   12m
worldcup-ingress-backend   <none>   worldcup-backend.apps.example.k8s.co.il   x.x.x.x   80, 443   12m

We now should have a running application:

Summary

In this continuation of How to prepare a NodeJS application for Kubernetes? Part 1, we focus on the practical aspects of deployment. We cover how to build a robust Dockerfile and prepare the essential deployment YAML files, ensuring your NodeJS application is Kubernetes-ready. At Octopus Computer Solutions, our Kubernetes expertise helps streamline the deployment process, ensuring your applications run efficiently and securely.

The entire git repository for files are at https://gitlab.octopuscs.com/dpointk/k8s-worldcup

Enjoy!