# Kubernetes manifests for a Node.js app

> The plain YAML baseline: a Deployment and a Service with probes, resources, security context and a named port. Kustomize and Helm build on it.

- Canonical: https://js-on-k8s.dev/recipes/manifests
- Site: JavaScript on Kubernetes (https://js-on-k8s.dev)
- Updated: 2026-09-10
- Tags: kubernetes, deployment, service, manifests
- Example: https://github.com/vojtechmares/js-on-k8s/tree/main/examples/full/k8s/base
- Full example: https://github.com/vojtechmares/js-on-k8s/tree/main/examples/full

This is the complete baseline in plain YAML. Every other recipe adjusts one part of it, and
the [Kustomize](/recipes/manifests/kustomize) and [Helm](/recipes/manifests/helm-chart) recipes below
package the same files for more than one environment.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  labels:
    app.kubernetes.io/name: app
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: app
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app
    spec:
      terminationGracePeriodSeconds: 30
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: ghcr.io/you/app:1.0.0
          ports:
            - name: http
              containerPort: 3000
          env:
            - name: PORT
              value: "3000"
            - name: NODE_ENV
              value: production
          resources:
            requests:
              cpu: "1"
              memory: 256Mi
            limits:
              memory: 256Mi
          startupProbe:
            httpGet: { path: /healthz, port: http }
            failureThreshold: 30
            periodSeconds: 2
          livenessProbe:
            httpGet: { path: /healthz, port: http }
            periodSeconds: 10
          readinessProbe:
            httpGet: { path: /readyz, port: http }
            periodSeconds: 5
          lifecycle:
            preStop:
              sleep:
                seconds: 5
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
---
apiVersion: v1
kind: Service
metadata:
  name: app
spec:
  selector:
    app.kubernetes.io/name: app
  ports:
    - name: http
      port: 80
      targetPort: http
```

## Notes

- Name the port and reference it by name in probes and the Service. Change the number in one place.
- `readOnlyRootFilesystem: true` works with Node.js as long as you do not write to disk.
  Mount an `emptyDir` at `/tmp` if a library needs it.
- Memory limit equals memory request. See [memory and CPU](/recipes/memory-and-cpu) for why.
- One full CPU requested, no CPU limit. Less than that throttles Node.js under load; see [memory and CPU](/recipes/memory-and-cpu).
- Expose through an Ingress or an HTTPRoute; see [networking](/recipes/networking). The Service is all the app needs to know about.

## In this section

- [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.
