
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Deploying Node.js microservices requires more than just a basic container; you need to understand how to properly run NestJS on Kubernetes to handle lifecycle events, memory limits, and graceful shutdowns. Many teams struggle because they treat NestJS like a stateless script rather than a managed application platform that requires specific orchestration signals. This guide provides the exact configuration patterns I use in production to ensure high availability and audit-ready compliance for enterprise workloads.
How do you optimize a NestJS Dockerfile for Kubernetes?
The foundation of any stable cluster is an immutable, minimal artifact. When you run NestJS on Kubernetes, your image size directly impacts scaling speed and attack surface. A common mistake is shipping the entire node_modules directory or using a full OS base image. In practice, I recommend a three-stage build process that separates dependencies, compilation, and runtime execution. This approach aligns with multi-stage build best practices and ensures your final artifact contains only what is strictly necessary to execute the compiled JavaScript.
Your final stage should use a distroless or Alpine base. Distroless images are preferred for SOC 2 and ISO 27001 environments because they lack shells and package managers, making post-exploitation significantly harder. Here is a production-grade Dockerfile pattern:
# Stage 1: Production Dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
# Stage 2: Build Application
FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build && npm prune --omit=dev
# Stage 3: Production Runtime
FROM gcr.io/distroless/nodejs22-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER nonroot
EXPOSE 3000
CMD ["dist/main.js"] Note the USER nonroot directive. Running as root inside a container is a critical security failure. Also, verify that your tsconfig.json outputs to a dist folder and that source maps are disabled in production to reduce image bloat and prevent code leakage.
How do you configure health checks and graceful shutdowns?
Kubernetes cannot manage what it cannot observe. To safely run NestJS on Kubernetes, you must expose standardized endpoints that the kubelet can poll. NestJS provides the @nestjs/terminus module specifically for this purpose. Without it, rolling updates will drop active connections, and failing pods may continue receiving traffic indefinitely.
Implementing Readiness and Liveness Probes
Liveness determines if the container needs restarting; readiness determines if it should receive traffic. These are distinct signals. A database connection failure should fail readiness but not necessarily liveness, unless the app cannot recover. Install the terminus package and create a health controller:
@Controller('health')
export class HealthController {
constructor(private health: HealthCheckService, private db: TypeOrmHealthIndicator) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database', { timeout: 300 }),
]);
}
} In your Helm values or Deployment manifest, map these endpoints precisely. If you are unsure about probe tuning, review my notes on debugging CrashLoopBackOff errors, as aggressive timeouts are a frequent cause of startup failures in NestJS apps with heavy module initialization.
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1 Handling SIGTERM Gracefully
When Kubernetes scales down or updates a pod, it sends a SIGTERM signal. NestJS does not handle this automatically. You must enable the shutdown hook in your main.ts bootstrap function. This allows the app to finish processing in-flight requests before exiting:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks(); // Critical for K8s
await app.listen(3000);
} Without enableShutdownHooks(), your API will abruptly sever connections during deployments, leading to 502 errors at the ingress layer. Combine this with a preStop sleep of 3–5 seconds in your pod spec to allow the service mesh or ingress controller time to update its endpoint list before the app stops accepting traffic.
How do you manage secrets and configuration in NestJS on Kubernetes?
Hardcoding configuration in containers violates twelve-factor principles and fails compliance audits. When you run NestJS on Kubernetes, all dynamic configuration must be injected externally. Use ConfigMaps for non-sensitive data like log levels and feature flags, and Secrets for database credentials and API keys. For teams managing sensitive data at scale, integrating Kubernetes secrets management best practices is essential to avoid storing unencrypted values in Git.
Use the official @nestjs/config module to validate environment variables at startup. This prevents the application from entering a half-configured state. Define a validation schema using Joi or Zod:
ConfigModule.forRoot({
validationSchema: Joi.object({
DATABASE_HOST: Joi.string().required(),
DATABASE_PORT: Joi.number().default(5432),
JWT_SECRET: Joi.string().min(32).required(),
LOG_LEVEL: Joi.string().valid('error', 'warn', 'info', 'debug').default('info'),
}),
}) If validation fails, NestJS throws immediately, triggering the liveness probe failure and preventing broken pods from serving traffic. For secret rotation, consider External Secrets Operator or Sealed Secrets so that raw credentials never touch your Git repository. This separation of concerns is mandatory for maintaining SOC 2 compliance in regulated environments.
How do you tune resources and autoscaling for NestJS?
Node.js memory management differs fundamentally from Java or Go. The V8 engine manages its own heap independently of the OS-level RSS. When you run NestJS on Kubernetes, setting memory limits too low causes OOMKilled crashes even when the container appears to have free RAM, while setting them too high wastes expensive cloud resources. Understanding resource limits and requests is critical for cost-efficient NestJS deployments.
| Parameter | Development / Low Traffic | Production Standard | High Performance |
|---|---|---|---|
| CPU Request | 100m | 250m | 500m |
| CPU Limit | 500m | 1000m | 2000m |
| Memory Request | 256Mi | 512Mi | 1Gi |
| Memory Limit | 512Mi | 1Gi | 2Gi |
| Node Options | --max-old-space-size=384 | --max-old-space-size=768 | --max-old-space-size=1536 |
Always set --max-old-space-size to roughly 75% of your container memory limit. This reserves headroom for native C++ bindings, buffers, and OS overhead. If your limit is 1Gi, set max-old-space-size to 768MB. Without this flag, V8 may attempt to allocate beyond the cgroup limit, resulting in immediate termination.
For autoscaling, rely on CPU utilization for NestJS rather than memory, as memory usage tends to be stable after warmup. Configure Horizontal Pod Autoscaler (HPA) with a target CPU utilization of 65–70%. Set a minimum replica count of 2 for production to survive node failures without downtime. Remember that NestJS has a cold start penalty; aggressive scale-from-zero strategies often hurt user experience unless you implement request buffering at the ingress level.
How do you deploy NestJS using Helm and GitOps?
Manual kubectl apply commands do not scale and leave no audit trail. To reliably run NestJS on Kubernetes, adopt Helm for packaging and ArgoCD or Flux for delivery. Helm templates allow you to parameterize environment-specific differences while keeping the core deployment logic consistent. If you are new to chart development, start with the patterns outlined in my Helm chart writing guide before customizing for NestJS specifics.
Structure your Helm chart to separate concerns. Keep the Deployment, Service, Ingress, and HPA as distinct templates. Use a values-production.yaml override file for production-specific settings like higher replica counts, stricter resource limits, and enabled PodDisruptionBudgets. Never put secrets in values files; reference external secret stores instead.
A critical operational detail is the PodDisruptionBudget (PDB). NestJS pods take time to warm up JIT compilation and establish database pools. During node maintenance or cluster upgrades, a PDB ensures at least one replica remains available:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: nestjs-api-pdb
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: nestjs-api Combine this with a rolling update strategy that sets maxUnavailable: 0 and maxSurge: 1. This guarantees zero-downtime deployments by ensuring new pods pass readiness checks before old ones terminate. Monitor your deployment velocity and rollback frequency; if rollbacks exceed 5%, your testing or staging parity needs improvement before optimizing further.
Next Steps for Production NestJS Deployments
Successfully continuing to run NestJS on Kubernetes requires ongoing observation and refinement. Start by implementing the multi-stage build and health checks described above, then graduate to GitOps-driven deployments with proper secret isolation. Monitor your V8 heap metrics alongside standard Kubernetes resource usage to catch memory leaks before they cause outages. If your team needs help architecting a compliant, scalable NestJS infrastructure or auditing an existing deployment, reach out to discuss your specific requirements.