# JavaScript on Kubernetes

> Practical, focused recipes for running Node.js and JavaScript applications on Kubernetes. Container images, signals, graceful shutdown, probes, manifests, Helm, Kustomize.

- Canonical: https://js-on-k8s.dev/
- Source: https://github.com/vojtechmares/js-on-k8s
- Full example: https://github.com/vojtechmares/js-on-k8s/tree/main/examples/full

Running Node.js on Kubernetes is mostly boring, and that is the point. A handful of
details decide whether your app restarts cleanly, drops requests on deploy, or gets
OOM-killed at 3 am. This site collects those details as short recipes.

## Principles

- **One process, started directly.** Run `node server.js`, not `npm start`. Signals must reach your code.
- **Shut down on purpose.** Handle `SIGTERM`, stop accepting connections, finish in-flight work, exit.
- **Tell Kubernetes the truth.** Separate liveness and readiness. Fail readiness before you stop.
- **Ship the runtime, not a distro.** Multi-stage builds ending in a distroless image. No shell, no package manager, non-root.
- **Set memory limits twice.** Once for the container, once for V8.
- **Configure from the environment.** Build once, promote the same image through environments.

## How to read this

Each recipe covers exactly one thing and fits on a screen. Snippets are copy-paste
ready and taken from the [full example](https://github.com/vojtechmares/js-on-k8s/tree/main/examples/full),
which applies all of them to a small HTTP service.

## For agents and tooling

Every page on this site is also plain Markdown. Request any URL with
`Accept: text/markdown`, or append `.md` to the path. A [llms.txt](/llms.txt) index is available too.

## Recipes

### Build

- [Run node directly, not npm start](https://js-on-k8s.dev/recipes/run-node-directly) - Make node PID 1 so Kubernetes signals reach your application code.
- [Container images for Node.js](https://js-on-k8s.dev/recipes/container-images) - What a production image should contain, and the recipes that get you there for plain Node.js, Bun, Next.js and TanStack Start.
  - [A minimal multi-stage Dockerfile](https://js-on-k8s.dev/recipes/container-images/dockerfile) - Install with npm ci, build in one stage, copy only what runs into the final image.
  - [Distroless instead of Alpine or Debian slim](https://js-on-k8s.dev/recipes/container-images/distroless-image) - Ship the Node.js runtime and your app, nothing else. No shell, no package manager, non-root.
  - [Private packages with Docker build secrets](https://js-on-k8s.dev/recipes/container-images/build-secrets) - Pass NPM_TOKEN as a BuildKit secret. Never put it in an ARG, an ENV, or a copied .npmrc.
  - [Build an image with Cloud Native Buildpacks](https://js-on-k8s.dev/recipes/container-images/buildpacks) - Get a production Node.js image without writing a Dockerfile, using pack and Paketo.
  - [Containerize a Hono API](https://js-on-k8s.dev/recipes/container-images/hono) - A Hono service on the Node.js adapter, built in two stages into a distroless image, with the Bun variant alongside.
  - [Containerize a Next.js app](https://js-on-k8s.dev/recipes/container-images/nextjs) - Build with output standalone, copy three folders into a distroless image, run server.js directly.
  - [Containerize a TanStack Start app with Nitro](https://js-on-k8s.dev/recipes/container-images/tanstack-start) - Add the Nitro Vite plugin, build a node-server bundle into .output, and run the entry with node in a distroless image.

### Run

- [Kubernetes manifests for a Node.js app](https://js-on-k8s.dev/recipes/manifests) - The plain YAML baseline: a Deployment and a Service with probes, resources, security context and a named port. Kustomize and Helm build on it.
  - [Kustomize base and overlays](https://js-on-k8s.dev/recipes/manifests/kustomize) - One base with the manifests, one overlay per environment that changes only the image tag, replicas and config.
  - [A minimal Helm chart](https://js-on-k8s.dev/recipes/manifests/helm-chart) - A chart with one Deployment, one Service, a PDB and a values file with only the knobs you actually turn.
- [Configuration from the environment](https://js-on-k8s.dev/recipes/config-from-environment) - Build one image, promote it through environments, and read every setting from env vars.
- [Kubernetes probes done right](https://js-on-k8s.dev/recipes/probes) - Liveness restarts, readiness routes. Give them different endpoints, keep dependencies out, and use readiness to shed load.
- [Graceful shutdown on SIGTERM](https://js-on-k8s.dev/recipes/graceful-shutdown) - Stop accepting connections, finish in-flight requests, then exit with a deadline.
  - [Graceful shutdown with Express](https://js-on-k8s.dev/recipes/graceful-shutdown/express) - app.listen() returns a plain Node.js http.Server. Close that, not the app.
  - [Graceful shutdown with Fastify](https://js-on-k8s.dev/recipes/graceful-shutdown/fastify) - Use app.close() with forceCloseConnections and onClose hooks, and listen on 0.0.0.0.
  - [Graceful shutdown with Hono](https://js-on-k8s.dev/recipes/graceful-shutdown/hono) - Hono is runtime-agnostic. Close the server the adapter gave you, on Node or on Bun.
  - [Graceful shutdown with Elysia](https://js-on-k8s.dev/recipes/graceful-shutdown/elysia) - Elysia runs on Bun. Call app.stop() on SIGTERM and ship it in the Bun distroless image.
  - [Graceful shutdown with NestJS](https://js-on-k8s.dev/recipes/graceful-shutdown/nestjs) - Turn on enableShutdownHooks() and use the lifecycle hooks to flip readiness and close resources in order.
  - [Graceful shutdown with Next.js](https://js-on-k8s.dev/recipes/graceful-shutdown/nextjs) - The standalone server already handles SIGTERM. Add readiness and cleanup around it, do not replace it.
  - [Graceful shutdown with TanStack Start and Nitro](https://js-on-k8s.dev/recipes/graceful-shutdown/tanstack-start) - Nitro's node server drains in-flight requests on SIGTERM. Tune its timeout and add readiness in the server entry.
- [Pod, Service, Ingress, Gateway API and NetworkPolicy](https://js-on-k8s.dev/recipes/networking) - How a request reaches your container, and why you should say who may talk to it.
- [Zero-downtime rolling updates](https://js-on-k8s.dev/recipes/zero-downtime-rollouts) - Rolling update strategy, a PodDisruptionBudget and spread across nodes so a deploy never drops requests.

### Measure and right-size

- [Log JSON lines to stdout](https://js-on-k8s.dev/recipes/logging) - One JSON object per line on stdout. No files, no log rotation, no agents inside the container.
- [Telemetry with OpenTelemetry](https://js-on-k8s.dev/recipes/telemetry) - Kubernetes collects nothing about your app. Export logs, metrics and traces yourself, in one standard.
- [Memory and CPU for Node.js Pods](https://js-on-k8s.dev/recipes/memory-and-cpu) - Measure before you set numbers. Then give Node.js a full CPU or two, size the heap under the memory limit, and re-measure after every runtime upgrade.

Each recipe is also available as Markdown: append `.md` to its URL, or request it with `Accept: text/markdown`.
