Skip to main content

Running a Node.js App Locally using Docker & Kubernetes

Aakash Verma

Introduction

In this article, we will build a simple Node.js App and run it locally using Docker & Kubernetes on the Minikube cluster.

What is Minikube?

In the simplest words, minikube is a tool that sets up a Kubernetes environment on a local PC or laptop.

Prerequisites

To follow this article, you must install the following things on your local machine.

Create a Node.js Application

I am inside one of the directories in my system. You can choose any directory on your machine.

cd Documents/Learning

Create a new directory and initialize the app

mkdir my-app && cd my-app
npm init -y

Create a new file index.js and paste the below code

Here, we will create an API /hello/:name/ which will print the name passed in the URL along with the pod name to which the request has hit. Through this, we would easily know how multiple pods are working.

const express = require('express');
const app = express();

app.get('/hello/:name', (req, res) => {
    const { name } = req.params;
    const podName = process.env.HOSTNAME || 'Unknown Pod'; // Retrieve the pod name from the environment variable
    res.json({ message: `Hello ${name} from ${podName}` });
});

const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

Test whether the project is running locally or not

Paste and run the below command into your terminal in the current working directory.

node index.js

You will see:

Server is running on port 8000

Let's test the API on the browser or any API-testing app like Postman. For simplicity, you can paste the URL to the browser of your choice and note the output.

Paste and hit the below URL to the browser.

http://localhost:8000/hello/Innoskrit

You will notice the below output.

{
  "message": "Hello Innoskrit from Unknown Pod"
}

Why Unknown Pod? The reason is simple. Because we don't have any HOSTNAME environment variable defined locally by default.

Let's Dockerize the app

Create a Dockerfile inside the root directory of the project and paste the following code snippet.

FROM node:14

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm install

COPY . .

EXPOSE 8000

CMD ["node", "index.js"]

Create a Docker image. Run the following command from inside the root directory of your project.

docker build --platform=linux/amd64 -t learnwithinnoskrit/my-app .

You can verify if the docker image is created by running the below command.

docker images

You will get to see the following output.

REPOSITORY          TAG       IMAGE ID       CREATED              SIZE
my-app              latest    eee505f4dfa7   About a minute ago   865MB

Let's run the same application by running this docker image instead of running the app using node index.js.

Paste and hit the below command in your terminal.

docker run -p 8000:8000 my-app

The above command will do the same as running this app using node index.js command.

8000:8000 = your local port:container port

Now try calling the same API from your browser again. You will see the following output.

{
  "message": "Hello Innoskrit from 291bc783ff84"
}

This time, you would see the pod ID because the code runs from inside the docker container and not locally.

Finally, we have verified the docker image runs correctly. Now we can proceed with Kubernetes things where we will create a Deployment file for this app and expose it as a NodePort K8s service to access the endpoints exposed by the service on the browser.

Creating K8s Deployment and K8s Service Yaml File

Creating a deployment.yaml file inside the root directory of your project.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3  
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: docker.io/learnwithinnoskrit/my-app
          ports:
            - containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
spec:
  selector:
    app: my-app
  type: NodePort 
  ports:
  - name: http
    port: 8000
    targetPort: 8000
    nodePort: 30080
    protocol: TCP

Hit the below command to apply this Kubernetes yaml file.

kubectl apply -f deployment.yaml

Now verify if the deployment and service are created.

kubectl get deployments

You will get to see something like this:

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
my-app             1/3     3            1           10s

Let's test if the service is created.

kubectl get services

You will get to see something like this:

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes             ClusterIP   10.96.0.1       <none>        443/TCP          135d
my-app-svc             NodePort    10.104.197.48   <none>        8000:30080/TCP   20s

Woah! You've come so far. It's time to see the output in the browser.

If you're on Mac, you might not be able to access this service directly. So you would need to hit one more command.

minikube service my-app-svc  --url
💡
If the above command fails, you will need to start Minikube. You can run 'minikube start'.

You would see something like this:

http://127.0.0.1:62711
❗  Because you are using a Docker driver on darwin, the terminal needs to be open to run it.

Just keep the terminal open. Open a new tab in the browser and hit the below URL. Replace the base URL with the one you got from the previous command.

http://127.0.0.1:62711/hello/Innoskrit

You will get to see something like this:

{
  "message": "Hello Innoskrit from my-app-85bb76fb86-lh54z"
}

You can see the Kubernetes Pod name in the response along with the name passed in the API call.

To confirm the pod name, just try one more command in your terminal.

kubectl get pods

You would see something like this:

NAME                                READY   STATUS    RESTARTS      AGE
my-app-85bb76fb86-l6ssz             1/1     Running   3 (17s ago)   1m10s
my-app-85bb76fb86-lh54z             1/1     Running   3 (17s ago)   1m10s
my-app-85bb76fb86-zwkcc             1/1     Running   3 (17s ago)   1m10s

And that's it. We were able to deploy the Node.js App locally using Docker and Kubernetes.

Why did we do minikube service my-app-svc --url?

Here is the detailed blog.

Kubernetes NodePort Service is not Exposed on Minikube Cluster - Solved
Problem Statement I have defined Deployment and Service on my machine in deployment.yaml as shown below. apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-app image: docker.io/username/my-app ports: - containerPort:

Thank you!