Running a Node.js App Locally using Docker & Kubernetes
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
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.
Thank you!