Deploying a Node.js To-Do App with Helm on Kubernetes


Introduction
In this guide, we will deploy a Node.js To-Do App on a Kubernetes cluster using Helm. The application runs on port 8000 and uses the Docker image sgaurav7/node-app
. We will configure health checks, a NodePort service, and ensure a seamless deployment.
1️⃣ Prerequisites
Before we begin, make sure you have:
✔ A running Kubernetes cluster
✔ Helm installed (helm version
)
✔ kubectl
access to your cluster
2️⃣ Create a Helm Chart
Run the following command to create a new Helm chart:
[root@k8s-master projects]# helm create todo-app
Creating todo-app
[root@k8s-master projects]# ls
mysql todo-app
[root@k8s-master projects]# cd todo-app/
[root@k8s-master todo-app]# ls
charts Chart.yaml templates values.yaml
[root@k8s-master todo-app]#
3️⃣ Define values.yaml
Update values.yaml
with the Docker image, replicas, and NodePort service:
replicaCount: 2
image:
repository: sgaurav7/node-app
pullPolicy: IfNotPresent
tag: latest
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
automount: true
annotations: {}
name: ""
podAnnotations: {}
podLabels: {}
podSecurityContext: {}
securityContext: {}
service:
type: NodePort
port: 8000
nodePort: 30007
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
resources: {}
livenessProbe:
httpGet:
path: /
port: 8000
readinessProbe:
httpGet:
path: /
port: 8000
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
volumes: []
volumeMounts: []
nodeSelector: {}
tolerations: []
affinity: {}
4️⃣ Configure Deployment (templates/deployment.yaml
)
Modify deployment.yaml
to use the correct image, port, and health probes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "todo-app.fullname" . }}
labels:
{{- include "todo-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "todo-app.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "todo-app.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "todo-app.serviceAccountName" . }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: todo-port
containerPort: {{ .Values.service.port }}
protocol: TCP
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
5️⃣ Configure Service (templates/service.yaml
)
Ensure the NodePort service exposes port 8000 correctly:
apiVersion: v1
kind: Service
metadata:
name: {{ include "todo-app.fullname" . }}
labels:
{{- include "todo-app.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: 8000
nodePort: {{ .Values.service.nodePort }}
protocol: TCP
name: node-port
selector:
{{- include "todo-app.selectorLabels" . | nindent 4 }}
6️⃣ Deploy the To-Do App Using Helm
First-Time Installation
[root@k8s-master todo-app]# helm install v1 . --namespace to-do --create-namespace
NAME: v1
LAST DEPLOYED: Mon Mar 17 04:03:07 2025
NAMESPACE: to-do
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export NODE_PORT=$(kubectl get --namespace to-do -o jsonpath="{.spec.ports[0].nodePort}" services v1-todo-app)
export NODE_IP=$(kubectl get nodes --namespace to-do -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
[root@k8s-master todo-app]#
Upgrade the Application
If you make any changes:
helm upgrade todo-app . -n to-do
7️⃣ Verify the Deployment
Check Helm Release
[root@k8s-master todo-app]# helm list -n to-do
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
v1 to-do 1 2025-03-17 04:03:07.477506603 -0400 EDT deployed todo-app-0.1.0 1.16.0
[root@k8s-master todo-app]#
Check Running Pods
[root@k8s-master todo-app]# kubectl get pods -n to-do
NAME READY STATUS RESTARTS AGE
v1-todo-app-5c87b864c-b7c7l 1/1 Running 0 55s
v1-todo-app-5c87b864c-gk7ph 1/1 Running 0 55s
[root@k8s-master todo-app]#
Check Service
[root@k8s-master todo-app]# kubectl get svc -n to-do
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
v1-todo-app NodePort 10.108.87.163 <none> 8000:30007/TCP 72s
[root@k8s-master todo-app]#
8️⃣ Access the To-Do App
Now your application is accessible via:
export NODE_PORT=$(kubectl get --namespace to-do -o jsonpath="{.spec.ports[0].nodePort}" services v1-todo-app)
export NODE_IP=$(kubectl get nodes --namespace to-do -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
📌 Replace <Node-IP>
with any Kubernetes worker node's IP.
[root@k8s-master todo-app]# export NODE_PORT=$(kubectl get --namespace to-do -o jsonpath="{.spec.ports[0].nodePort}" services v1-todo-app)
[root@k8s-master todo-app]# export NODE_IP=$(kubectl get nodes --namespace to-do -o jsonpath="{.items[0].status.addresses[0].address}")
[root@k8s-master todo-app]# echo http://$NODE_IP:$NODE_PORT
http://10.12.23.19:30007
[root@k8s-master todo-app]#
Now our application is accessible on the mentioned URL - http://10.12.23.19:30007
Search this URL on webpage.
Hurray!! We successfully deployed our application on k8s-cluster using helm.
✅ Summary
Created a Helm chart for the To-Do App
Configured health probes to ensure reliability
Deployed the app with a NodePort service (port 8000)
Verified everything using
kubectl
andhelm
Accessed the app using
http://<Node-IP>:30008
🎯 Now your To-Do App is successfully running on Kubernetes using Helm! 🚀
Let me know if you need any modifications. 😊
Subscribe to my newsletter
Read articles from Siddhartha Gaurav directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Siddhartha Gaurav
Siddhartha Gaurav
I'm a passionate DevOps engineer with a knack for streamlining development workflows and ensuring seamless deployment pipelines. With experience in managing cloud infrastructure, implementing DevOps best practices, and leveraging automation tools, I thrive on tackling complex challenges and driving efficiency in software delivery. Let's connect and explore how I can contribute to your projects!