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.
