Run NestJS on Kubernetes

Khimananda Oli 8 min read Programming and Languages
Run NestJS on Kubernetes

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.

Stage 1: Depsnpm ci --only=productionCache node_modulesStage 2: Buildnpm run buildCompile TS to JSStage 3: Runtimegcr.io/distroless/nodejsCopy dist + prod deps
Optimized three-stage Docker build strategy for NestJS containerization

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.

ConfigMapLOG_LEVEL, NODE_ENVSecret (Vault/ESO)DB_PASS, JWT_KEYKubeletMounts & Env InjectionNestJS Pod@nestjs/config
Secure configuration injection architecture for NestJS microservices

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.

ParameterDevelopment / Low TrafficProduction StandardHigh Performance
CPU Request100m250m500m
CPU Limit500m1000m2000m
Memory Request256Mi512Mi1Gi
Memory Limit512Mi1Gi2Gi
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.

Git RepoHelm Chart + ValuesCI PipelineTest & Push ImageArgoCD / FluxSync & ReconcileK8s ClusterNestJS Pods Live
End-to-end GitOps pipeline for automated NestJS deployments

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.

Frequently Asked Questions

Use node:22-alpine or distroless/nodejs22-debian12 for minimal attack surface. Alpine reduces image size to under 150MB, speeding up pod scheduling and reducing storage costs across your cluster nodes significantly.

Expose /health endpoints using @nestjs/terminus. Configure livenessProbe and readinessProbe in your deployment manifest pointing to these HTTP paths with appropriate initialDelaySeconds to prevent premature restarts during application bootstrap.

Yes. Multi-stage builds separate compilation from runtime, excluding TypeScript source and dev dependencies. This produces smaller, secure images containing only compiled JavaScript and production node_modules for faster Kubernetes deployments.

Store secrets in Kubernetes Secrets and config in ConfigMaps. Mount them as environment variables or files. Never bake credentials into container images; use external secret managers like External Secrets Operator for production safety.

Start with 256Mi memory request and 512Mi limit for typical APIs. Set CPU requests at 250m with 1000m limits. Monitor actual usage via Prometheus and adjust based on p99 latency and OOMKill events.

Enable terminationGracePeriodSeconds in your pod spec. NestJS automatically listens for SIGTERM when enableShutdownHooks is called. Ensure HTTP servers stop accepting new connections while finishing in-flight requests before the deadline expires.

Yes, when paired with proper metrics. Use KEDA or native HPA targeting custom metrics like request queue depth or CPU utilization. Avoid scaling solely on memory since Node.js garbage collection causes misleading spikes.

Use port-forwarding to attach VS Code debugger remotely. Enable --inspect flag conditionally via environment variable. For production issues, rely on structured logging and distributed tracing with OpenTelemetry rather than interactive debugging sessions.

NGINX Ingress Controller or Traefik handle most NestJS routing needs efficiently. Configure path-based routing, TLS termination, and rate limiting at the ingress layer to offload cross-cutting concerns from your application code entirely.

Precompile TypeScript during CI, not at runtime. Use eager module loading instead of lazy loading where possible. Consider keeping minimum replica counts above zero or using Knative for scale-to-zero workloads with acceptable latency tradeoffs.

Yes, using Nx or Turborepo to build individual apps separately. Deploy each NestJS app as an independent container sharing common libraries. This avoids rebuilding entire monorepos and enables granular scaling per service boundary.

Scan images with Trivy or Grype in CI pipelines. Run containers as non-root users with readOnlyRootFilesystem enabled. Apply PodSecurityPolicies or Pod Security Standards to enforce least privilege across all NestJS namespaces.

Output structured JSON logs to stdout/stderr only. Let Fluent Bit or Vector collect and forward to your observability stack. Include trace IDs and correlation fields to link logs across distributed NestJS services effectively.

Run TypeORM or Prisma migrations as Kubernetes Jobs before deploying new application versions. Use init containers or Helm pre-upgrade hooks to ensure schema changes complete successfully before pods serving traffic start up.

Check kubectl describe pod for OOMKilled or failed probe errors. Increase memory limits if heap exhaustion occurs. Adjust probe timeouts if startup takes longer than expected. Review application logs for unhandled exceptions causing crashes.