Skip to content
WhatsApp
AI StudioPricing
DevOps12 min read2026-09-11

Docker and Kubernetes Best Practices for Production Node.js & Go Microservices

Stop shipping bloated, insecure containers. Discover multi-stage Docker builds, non-root user execution, Kubernetes probes, resource requests/limits, and graceful shutdown handlers.

Alamgir Kabir
Chief Architect & Founder
Docker and Kubernetes Best Practices for Production Node.js & Go Microservices

1. The Anatomy of a Flawed Dockerfile

Most container images in early-stage startups suffer from the same set of critical vulnerabilities:

• Running as `root`: If an attacker exploits a remote code execution bug in an npm package, they obtain root access on the host node.

• Massive Bundle Size: Copying test files, build tools (C++ compilers, python), and source code results in 1.4GB images that take minutes to pull across clusters.

• Ignoring Zombie Processes: Node.js does not run as an init process (`PID 1`), causing unhandled SIGTERM signals and orphaned zombie processes during container shutdown.

Adopting production container best practices transforms fragile 1.2GB containers into secure, sub-60MB distroless images that boot in 300ms.

2. Production Multi-Stage Dockerfile for Node.js / TypeScript

Here is an enterprise-grade multi-stage Dockerfile that compiles TypeScript, prunes devDependencies, runs as an unprivileged user, and leverages dumb-init for proper signal handling:

Dockerfile
dockerfile
# Stage 1: Build & Compile
FROM node:22-alpine AS builder
WORKDIR /app
RUN apk add --no-cache libc6-compat

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build && npm prune --production

# Stage 2: Minimal Distroless Production Runner
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN apk add --no-cache dumb-init

# Create unprivileged system user
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 appuser

COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json

USER appuser
EXPOSE 3000
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]

3. Kubernetes Liveness vs Readiness vs Startup Probes

Misconfigured health checks in Kubernetes cause cascading outages. Understanding the distinct purpose of each probe is essential:

• Startup Probe: Checks if a slow-booting application has finished initializing. Disables liveness and readiness probes until it passes, preventing Kubernetes from prematurely killing pods during boot.

• Readiness Probe: Checks if the pod is currently able to receive user traffic. If a pod's database connection drops temporarily, the readiness probe fails and Kubernetes immediately removes the pod from load balancer routing without restarting the process.

• Liveness Probe: Checks if the container process has completely deadlocked. If it fails, Kubernetes restarts the pod.

Important Consideration: Never connect external database health checks to your Liveness probe! If your database has a transient glitch, all your web pods will restart simultaneously, worsening the outage.

4. Graceful Shutdown & Zero-Downtime Rolling Deploys

When Kubernetes terminates a pod during a deployment, it sends a `SIGTERM` signal followed by a `SIGKILL` after `terminationGracePeriodSeconds` (default 30s).

If your application does not catch `SIGTERM`, active customer requests are severed instantly, resulting in 502 Bad Gateway errors.

Proper graceful shutdown in Node.js stops accepting new connections, drains existing HTTP requests, closes database connection pools, and exits cleanly within a 15-second grace window.

Frequently Asked Questions

Why should I use `dumb-init` with Node.js in Docker?

Node.js was not designed to run as PID 1. It does not forward POSIX signals to child processes or reap zombie processes. `dumb-init` acts as a lightweight init system that handles signal forwarding correctly.

How should I set CPU and Memory requests/limits in Kubernetes?

Set CPU requests based on average utilization and leave CPU limits unset (or generous) to avoid unnecessary CPU throttling. Always set hard Memory limits to prevent a single pod with a memory leak from consuming the entire node.

What is a Distroless container image?

Distroless images contain only your application binary and runtime dependencies, without a Linux shell, package manager, or utility binaries, drastically shrinking the attack surface.

Topics:#docker kubernetes production#multi stage docker build nodejs go#kubernetes resource requests limits#graceful shutdown nodejs docker#kubernetes liveness readiness probes#secure container images distroless#microservices production deployment

Want this thinking applied to your product?

Spin up a prototype in minutes, or brief our team and get a scoped plan within 24 hours.