Previously, we have learned the components of Kubernetes and learned how to spin up a mini Kubernetes cluster in our system using minikube. This article will learn how to deploy a Node.js application in the local minikube Kubernetes cluster. We’ll do this demonstration on Windows 10 Pro laptop.
Prerequisites
Follow the below steps to build and deploy a simple Node.js application in the local minikube Kubernetes cluster.
Build Node.js application
Create a folder with the name nodekube in D:\ drive (or any drive of your choice). Open a command prompt and traverse to D:\nodekube and execute the npm init command. This command will ask for information about the application we will build. If you are happy with the default values provided by the wizard, just press Enter, otherwise enter your preferred values. It’ll create a package.json file inside the current directory when wholly executed. This file holds various metadata relevant to the project. This file also contains information that helps npm to identify the project. It also handles the dependencies of the project. For reference, check the snip of the application I created.

We’ll now write a straightforward script. When executed, it’ll render Hello World from Kubernetes! Message. Please copy the below code and save it as index.js inside the nodekube folder.
1 2 3 4 5 6 7 8 9 10 |
const http = require('http'); const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World from Kubernetes!'); }); server.listen(8080, function() { console.log('Server running on port 8080'); }); |
Now, go back to the command prompt, traverse to the nodekube folder, and issue this command: node index.js. Then, open a browser and hit http://localhost:8080. You will see Hello World from Kubernetes!.
Dockerize nodejs application
Our Node.js application is now ready. But to run it in Kubernetes, we’ll need to create an image. We’ll then deploy this image in the local minikube Kubernetes cluster and run it as a docker container. Let’s write a Dockerfile and save it in the same D:\nodekube folder.
1 2 3 4 5 6 7 8 9 |
FROM node:latest # Create work directory that will contain source code and other details WORKDIR /usr/src/app COPY package.json ./ RUN npm install # Bundle application source code COPY . . EXPOSE 8080 CMD ["node", "index.js"] |
We’ll now launch the command prompt, traverse to D:\nodekube folder and build an image by executing the following command :
1 |
docker build -t msadrud/hello-world-node:v1 . |
Let’s execute the following command to instantiate the image and run the Node.js application locally in a docker container.
1 |
docker run -p 8081:8080 msadrud/hello-world-node:v1 |
Let’s go to the browser and hit http://localhost:8081. We’ll see the same output as before; however, this time from the container that is running locally.
I have written a detailed blog on how to containerize a node.js application with explanation of every tiny detail. Should you have any question on the above steps, you can head there for clarification.
https://iteritory.com/dockerize-nodejs-web-application-step-by-step-guide/
Push the image in docker hub
We’ll use the docker hub as our container registry of the local Kubernetes cluster run by minikube. This means Kubernetes will fetch the docker image from the docker hub container registry and create a Pod to run the container. Hence, We’ll now push the above-created image to the docker hub. Please follow the below steps:
- If you don’t have a docker id already, head to https://hub.docker.com/ and sign up.
- Then sign in with your credetial and click on Repositories and then Create Repository.
- Make the repository name as hello-world-node. You can refer to how I created:

- Now, we’ll login to docker hub using the command line. It’ll prompt to enter the docker hub id and password. Key in those information to login.
1 |
docker login |

- Once logged in successfully, we’ll issue the following command to push the image (that we just created above) to the docker hub.
1 |
docker push msadrud/hello-world-node:v1 |
- After successful execution, we’ll see the image got pushed to the docker hub. Please refer to the snippet below. This is after I successfully pushed the locally built image to the Docker hub using the above command:

Start the Kubernetes cluster
Now, we’ll start our local Kubernetes cluster run by minikube. Let’s issue the following command to do so:
1 |
minikube start |
If you have not already installed minikube or have question about minkube installation process etc, please refer to my blog on how to install minkube in your local system.
https://iteritory.com/install-minikube-in-windows-10-laptop-step-by-step-tutorial/
Deploy the Docker image in the local Kubernetes cluster
At this point, we have developed the application, created an image from this application, pushed the image in the docker hub container registry. Our goal is to instantiate this image build and run it inside a container. In Kubernetes, the container does not run on its own; instead, it’s encapsulated into a Pod. Pods are the smallest object that we can create and manage in Kubernetes.
Kubernetes takes YAML-based configuration files as input and creates different objects like Pods, replicas, services, etc. In the following section, we’ll define a Pod configuration.
Define a YAML configuration for Pod
A Kubernetes object definition contains four root-level essential fields:
apiVerson
This key refers to the version of Kubernetes API that Kubernetes will use to create an object. Depending on what object we want to build, Kubernetes has different versions of APIs. In this case, to create a Pod object, we’ll use its value as v1.
kind
This key informs Kubernetes what type of object we are going to create. In this case, we are creating a Pod object. Hence, we’ll use its value as Pod. Other possible values are Pod, Job, ReplicaSet, etc.
metadata
The value of the metadata field is a dictionary, unlike apiVersion and kind where we supplied string value. This field describes some data about the object to be created. It also provides a means to identify it uniquely. In our case, we’ll define the following:
- name A name that uniquely identifies the pod. In our case, we’ll name it as helloworld-pod
- labels: These are custom key/value pairs that are assigned to an object as its property. We can use labels to organize the objects and also to group a similar type of pods in a cluster. It can help us identify and query the related objects. For example, in a retail enterprise, we can have multiple different modules like order-management, inventory-management etc. We can assign a label of inventory-management to a group of pods that run the inventory related services. There is no limitation on the number of labels we can assign. In our scenario, let’s add two labels for demonstration purpose:
- release: beta
- type: poc
spec
Depending on the kind of object we create, the content of this field describes the specification of that object. Kubernetes understands the desired state of the object through the configurations in the spec section. While defining the pod object, we use this section to tell Kubernetes what containers will be part of the Pod, their memory, storage requirement, etc. In this POC, our need is simple. We have only one container to run in the Pod that we’ll define. Spec is a dictionary. Let’s take a look at the values we will use:
- replicas: This field defines how many instances of this Pod we want to run. For this POC, let’s create just one Pod to start with. So, the value will be 1.
- containers: This segemnt is a YAML list/array because it can define more than one container if needed. The properties of each item in this list is again a dictionary. We’ll use following properties:
- name: Each container in a pod must have a unique name and it can’t be updated. In our case, we’ll keep the value as helloworld-container.
- image: In this field, we’ll mention the image infromation which in our case is msadrud/hello-world-node:v1. This is the image we have pushed into docker hub just a while back.
- ports: This segment is a YAML list/array that defines the list of ports to be exposed from the container. Exposing a port here gives the system additional information about the network connections a container uses. We’ll use only following property in the ports dictionary:
- containerPort: The value tells kubernetes the port number to expose on the Pod’s IP address. This must be a valid port number, 0 < x < 65536. So, here we’ll use port number 8080 which is the port, we have exposed from our code.
Now, putting all these elements together, the following is how our Pod configuration looks like.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
apiVersion: v1 kind: Pod metadata: name: helloworld-pod labels: release: beta type: poc spec: containers: - name: helloworld-container image: msadrud/hello-world-node:v1 ports: - containerPort: 8080 |
Define a YAML configuration for Deployment
We can use the above configuration snippet to create Pods manually easily. But this way, the management (e.g., upgrade, update, rollback, scale-up/down, etc.) of these Pods will be manual. Instead, we can use a Deployment object that defines a Pod’s desired state and let Kubernetes take care of the Pod management in the cluster. We’ll learn how to use this k8s object as part of this POC.
Let’s create a Deployment configuration file with the name hello-world-deployment-def.yaml in the D:\nodekube folder. As it is for any other k8s definition file, this one too has four main sections:
- apiVersion: We’ll use the value apps/v1
- kind: This value will be Deployment
- metadata:
- name: We’ll name it as helloworld-deployment
- labels: We’ll create following two custom labels
- release: beta
- type: poc
- spec:
- replicas: This field defines how many instances of this Pod we want to run. For this POC, let’s create just one Pod to start with. So, the value will be 3.
- selectors: The value of this section helps us to group which all Pods fall under it. The value of selector needs to be written as matchLabels. The matchLabels seclector matches labels written under it with the label of the Pods. In our Poc, we’ll use the followings as as its value:
- release: beta
- type: poc
- template: In this section, we need to provide the Pod template. We have already created a Pod defintion above. WE’ll use that configuration except for the first two lines.
Combining all these, let’s take a look at what the complete Deployment definition looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
apiVersion: apps/v1 kind: Deployment metadata: labels: release: beta type: poc name: helloworld-deployment spec: replicas: 3 selector: matchLabels: type: poc template: metadata: labels: release: beta type: poc name: helloworld-pod spec: containers: - image: "msadrud/hello-world-node:v1" name: helloworld-container ports: - containerPort: 8080 |
Create Deployment in kubernetes cluster
We will open a command prompt and execute the following command to create a deployment in the locally run k8s cluster.
1 |
d:\nodekube>kubectl apply -f hello-world-deployment-def.yaml |
Upon successful execution of the command, we’ll see the following:
1 |
deployment.apps/helloworld-deployment created |
We can issue the kubectl get pods to deploy command to get the pod and deployment status. It may take a while to bring the pod to running status, so have patience!
1 2 3 4 5 6 7 8 |
D:\nodekube>kubectl get pods,deploy NAME READY STATUS RESTARTS AGE pod/helloworld-deployment-694b47f7bc-mhjfm 1/1 Running 0 3m7s pod/helloworld-deployment-694b47f7bc-v8rqw 1/1 Running 0 3m7s pod/helloworld-deployment-694b47f7bc-whlbl 1/1 Running 0 3m7s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/helloworld-deployment 3/3 3 3 3m7s |
At this point, if we visit the Kubernetes dashboard, we’ll see something like the following. It’s interesting to note that k8s created a replicaset in the process (more about it in some other blog).

Expose the nodejs application outside cluster
These Pods that we just created are now running inside our local cluster. They have their own IPs that are accessible only from inside the cluster. The Pods are ephemeral to add to the complexity; their IPs are not static either. When a Pod is recreated, they can get new IPs. We can also have multiple replicas of these Pods running in the cluster spanning more than one node.
With such complexities, the question is, how do we discover and access the application from outside of the cluster familiarly and consistently without having to worry about the complexities we just discussed above? Well, Kubernetes addresses this problem with Services. A service in Kubernetes is an object to expose an application (be enabling network access) running on a group of Pods deployed in a cluster.
In Kubernetes, there are different types of services available to choose from:
- NodePort
- ClusterIP
- LoadBalancer
- ExternalName
We’ll use the NodePort service type to expose the API in this POC. In some later blogs, I’ll try and cover the other options.
Typically, we use NodePort to expose a service via a static port accessible on each node’s IP. It maps a port on the node to the port on the pod. In the NodePort setup, we deal with three different types of ports:
- NodePort: A port on the Node. This enables the access to service from external consumers. The range of NodePort values is from 30000 to 32767.
- port: This is the port on the Service object. It exposes the service within the cluster. Other pods in the cluster can communicate with the service using this port. The port basically redirects the traffic from service to the container port.
- targetPort: This is the port where the application is running in the container.
Define a YAML configuration for NodePort Service
Let’s create a Service configuration file with the name hello-world-service-def.yaml in the D:\nodekube folder. As it is for any other k8s definition file, this one too has four main sections:
- apiVersion: We’ll use the value v1
- kind: This value will be Service
- metadata:
- name: We’ll name it as helloworld-service
- spec:
- type: This field defines which type of serice we’ll create. We’ll use NodePort.
- ports: This is where we configure the 3 types of ports that we discussed above. this section is an array. We can have multiple such port mapping in a single service.
- targetPort: The value of this field will be 8080. This is the port where our nodejs application is running in the Pod. This is an optional field. If not mentioned, the value of port will be assigned for targetPort.
- port: We’ll set this value as 80. This is a mandatory field.
- nodePort: We’ll set this value as 30001. The range of valid values are from 30000 to 32767. This is an optional field. If we don’t mention this value in the service defintion file explicitly, an available port will be randomly selected.
Looking at configuration carefully, we have mapped a Service port 80 to a Pod targetPort 8080. But of which Pod? There can be many Pods running different applications on port 8080. We use labels and selectors to bridge this missing link.
- selectors:
- The value of this section helps us to link the Pod with the Service. So, we’ll just copy the labels that we mentioned in the Pod definition file.
- release: beta
- type: poc
- The value of this section helps us to link the Pod with the Service. So, we’ll just copy the labels that we mentioned in the Pod definition file.
Combining all these, let’s take a look at what the complete Service definition looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
apiVersion: v1 kind: Service metadata: name: helloworld-service spec: type: NodePort ports: - targetPort: 8080 port: 80 nodePort: 30001 selector: release: beta type: poc |
Create Service in kubernetes cluster
We will open a command prompt and execute the following command to create a NodePort Service in the locally run k8s cluster.
1 |
d:\nodekube>kubectl apply -f hello-world-service-def.yaml |
Upon successful execution of the command, we’ll see the following:
1 |
service/helloworld-service created |
We can issue the kubectl get services command to get the service we just created.
1 2 3 4 |
D:\nodekube>kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE helloworld-service NodePort 10.105.84.135 <none> 80:30001/TCP 81s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 105d |
How to access the application running in minikube?
The minikube VM is exposed to the host system via a host-only IP address that we can obtain with the minikube ip command. We can access any services of type NodePort over that IP address on the NodePort.
1 2 |
D:\nodekube>minikube ip 192.168.215.42 |
We can also get the URL by executing the following command:
1 2 |
D:\nodekube>minikube service --url helloworld-service http://192.168.215.42:30001 |
Now, if we hit this URL in the browser, we’ll see the response from the nodejs application running inside a Pod in our local Kubernetes cluster.

Well, that’s it!!
GitHub link
You can find the codes written here in the GitHub link: https://github.com/msadrud/kubernetes-nodejs-hello-world.
Conclusion
I hope you learned how to deploy a simple Node.js application in the local minikube Kubernetes cluster and expose it for external access. Stay tuned for more exciting articles around Kubernetes.