mdashikjs/blog
All posts
Deploying Node.js to Kubernetes: A Practical Guide
DevOps

Deploying Node.js to Kubernetes: A Practical Guide

DevOps5 min

Deploying Node.js to Kubernetes: A Practical Guide

Kubernetes has a steep learning curve, but for a Node.js backend the core concepts boil down to four manifests. Here is the minimal setup that got us to production with rolling updates, health checks, and auto-scaling.

KubernetesNode.jsDevOpsDeployment
Share:

Start with the Deployment

A Deployment tells Kubernetes what container to run and how many replicas to keep alive:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.2.0
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 20

Health checks are not optional

The readinessProbe tells Kubernetes when the pod is ready to receive traffic. The livenessProbe tells it when the pod is stuck and needs a restart. Without these, Kubernetes sends traffic to pods that are still booting or silently deadlocked.

Your /health endpoint should check downstream dependencies:

app.get('/health', async (req, res) => {
  try {
    await db.raw('SELECT 1');
    res.json({ status: 'ok' });
  } catch {
    res.status(503).json({ status: 'unhealthy' });
  }
});

Resource limits prevent noisy neighbors

Without resources.limits, one runaway pod can starve the entire node. Set limits based on your load testing. For a typical NestJS API, 512Mi memory and 500m CPU is a reasonable starting point.

Service and Ingress

The Service gives your pods a stable internal DNS name. The Ingress exposes it to the outside world:

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts: [api.example.com]
      secretName: api-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

Auto-scaling with HPA

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

This scales between 3 and 10 pods based on CPU utilization. For Node.js, CPU is usually the right metric because the event loop saturates before memory does.

Rolling updates for free

By default, Kubernetes rolls out new versions one pod at a time, waiting for each to pass its readiness probe before proceeding. No downtime. No PM2 reload tricks. It just works — as long as your health checks are honest.

MA

Written by Md Ashik

Senior Software Engineer building reliable backends. I write about the practical tradeoffs behind shipping software that holds up in production.