Docker & Containers

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Node.js 26 (26.10.0, released 2026-09-21, the Current line that becomes LTS on 2026-10-28), with the differences of Node.js 24 "Krypton" (24.21.0, Active LTS) and Node.js 22 "Jod" (22.23.3, Maintenance) called out; versions and stability levels were checked against the official documentation on 2026-10-04, and every Docker example below was built and run with Docker Desktop 29.8.1 on the node:26-slim image, and the server was also run on the node:24 and node:22 images. The Kubernetes manifest was parsed but not applied, because no cluster was available; it is labelled as such. A container is not a Node.js feature: the runtime does not know it is in one, and everything it does (listening, reading process.env, handling signals, writing to stdout) is the same as on a laptop. What changes is the environment around the process: it is started by a runtime that sends it signals, it is usually process number 1 in its own namespace, it sees a memory limit, and an orchestrator decides when it is ready and when it must die. This page covers only those Node-specific consequences. The general material lives in the Docker and Kubernetes sections and is linked, not repeated. The running scenario is the Bookshelf application described in Introduction & Architecture; the server used here is a small variant of the one in HTTP Server Fundamentals, extended with health endpoints.

The version and image map

Table 1. Container-related facts on Node.js 26, 24 and 22
Topic Status Version notes

Official node image on Docker Hub (variants <version>, <version>-slim, <version>-alpine)

Maintained by the Node.js Docker team

Tags exist per major line and per exact release. node:26-slim was Debian 13 "trixie" when checked (observed in /etc/os-release). The default user is root, and a non-root user named node (uid 1000, observed) is predefined in every variant.

npm ci --omit=dev

Stable (npm 10 and 11)

--omit=dev replaces the older --production. npm 11.19.1 ships with Node.js 26.10.0; Node.js 22 ships npm 10.9.9, which accepts the same flags. npm 12 (12.2.0 on 2026-10-04) is not bundled with any Node.js release yet.

Corepack in images

Removed from the distribution

Not bundled from Node.js 25, so corepack was not found in node:26-slim (observed). Install pnpm or Yarn explicitly, see Installing Node.js.

node --run <script>

Documented CLI option (no stability label in the CLI page)

Added in v22.0.0; runs a package.json script without npm. Convenient locally; it is not a good container entrypoint, see PID 1, signals and shutdown.

process.on('SIGTERM')

Stable

Works on every line. Node.js installs no default SIGTERM handler, so the kernel’s behaviour for PID 1 decides what happens without one.

--max-old-space-size-percentage

Documented V8 option (CLI page)

Listed in the Node.js 26, 24 and 22 CLI documentation, and accepted by all three images in the runs below.

server.close() and closeIdleConnections()

Stable

Close idle keep-alive connections; used by the shutdown code below on 26, 24 and 22. See HTTP Server Fundamentals.

Choosing the base image

The official image is published in three families. Choose by what the application needs at build time and run time, not by size alone.

Table 2. Variants of the official node image
Tag What it is Use it when

node:26

The full Debian-based image with compilers and common build tools. About 1.8 GB on disk here.

Building native add-ons in a build stage; never as the final image.

node:26-slim

Debian with only what Node.js needs to run. About 375 MB on disk here, with the app and its production dependencies.

The default recommendation for the runtime stage: glibc, so prebuilt native binaries from npm work.

node:26-alpine

Alpine Linux with musl libc.

Smallest images, when every dependency ships a musl build or compiles cleanly; native add-ons that assume glibc are the usual failure.

Tags such as node:26 float: they move to each new patch release, which is convenient for security fixes and surprising for reproducibility. Pin the major (node:26-slim) for a rolling patch, or an exact version for a pinned build, and let a bot such as Renovate or Dependabot propose updates. Digest pinning (node:26-slim@sha256:…​) gives the strongest guarantee. Image Best Practices and Supply-Chain Security covers tags, digests and scanning in general.

The Node.js multi-stage Dockerfile

The generic mechanics of multi-stage builds, layers and the build cache are in Dockerfile Fundamentals and Build Cache and Multi-Stage Builds. For Node.js the recipe is short: install only production dependencies in one stage with npm ci --omit=dev, copy the result into a slim runtime stage, and drop root with USER node.

Dockerfile for the Bookshelf server
# syntax=docker/dockerfile:1

ARG NODE_VERSION=26

FROM node:${NODE_VERSION}-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev

FROM node:${NODE_VERSION}-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node package.json ./
COPY --chown=node:node src ./src
USER node
EXPOSE 3000
CMD ["node", "src/server.js"]

Built with docker build -t bookshelf ., the image ran as uid 1000 with NODE_ENV=production, and its node_modules held the 13 production packages and no prettier (the dev dependency). A single-stage image that copies everything, runs npm install and keeps FROM node:26 (root user, dev dependencies included, full Debian) was 1.81 GB against 377 MB for the multi-stage one in this experiment. Each line has a reason.

Table 3. Why each Dockerfile line is there
Line Reason

ARG NODE_VERSION=26

One place to change the runtime line. --build-arg NODE_VERSION=24 builds the same file on Node.js 24, and 22 on Node.js 22, which is how an application is tested on the next LTS before upgrading. The FROM lines of both stages use it, so the stages cannot drift apart.

COPY package.json package-lock.json ./ before the source

Dependencies change less often than code. Copying only the manifests first keeps the npm ci layer cached until a manifest changes.

npm ci --omit=dev

Installs exactly what the lockfile says and fails if package.json and the lockfile disagree, so the image is reproducible; it deletes any existing node_modules first. --omit=dev leaves test, lint and build tools out of the final image. See npm & Package Management.

--mount=type=cache,target=/root/.npm

A BuildKit cache mount keeps npm’s download cache between builds without storing it in a layer. It needs the # syntax=docker/dockerfile:1 line on older builders.

Two FROM lines

Anything installed in the first stage (compilers for native add-ons, a TypeScript build, dev dependencies) stays there. Only the COPY --from=deps result reaches the final image.

ENV NODE_ENV=production

Frameworks and libraries branch on it (Express, for example, enables view caching and terser errors). Set it in the runtime stage. Setting it before npm ci would also skip dev dependencies, which is why the build stage uses the explicit flag instead. See Production Deployment.

--chown=node:node

Files copied by COPY belong to root by default. With USER node the process can read them either way, but owning them lets the app write inside its own directory when it needs to and keeps the intent explicit.

USER node

The official image predefines this unprivileged user. Kubernetes' runAsNonRoot check cannot verify a user given by name, so the manifest below also sets runAsUser: 1000, the uid observed for node. A process that is compromised is then not root inside the container.

EXPOSE 3000

Documentation only; it publishes nothing. Publishing happens with -p or in the orchestrator.

CMD ["node", "src/server.js"]

Exec form, so Node.js is the container’s main process and receives signals directly. See PID 1, signals and shutdown.

If the project is TypeScript or needs a bundler, add a build stage between the two, run npm ci there (dev dependencies included), build, and copy only dist/ into the runtime stage. On Node.js 24 and 26 plain .ts files can also run through type stripping (see TypeScript in Node.js), which removes the build stage for projects that stay within erasable syntax.

The .dockerignore file

docker build sends the build context (the directory) to the builder. COPY . . would then copy everything in it, including a developer’s node_modules, which may hold binaries built for macOS, and files that must never reach an image such as .env or .git. A .dockerignore file in the context root lists what to leave out.

.dockerignore for a Node.js project
node_modules
npm-debug.log*
.git
.gitignore
.env
.env.*
Dockerfile
compose.yaml
.dockerignore
coverage
test
*.md

node_modules is the important line: with it ignored, the deps stage is the only place that creates it, inside the container’s own operating system. The .env lines keep local secrets out of every layer, and exclusion also keeps the context small (12 MB of host node_modules in the scratch project would otherwise be uploaded on every build). Because the Dockerfile in this page copies only package.json, the lockfile and src, the file is a safety net here; with a COPY . . style Dockerfile it becomes essential.

PID 1, signals and shutdown

Inside a container the first process, whatever it is, is PID 1 of the container’s PID namespace. Two things are special about PID 1 on Linux: the kernel does not apply the default action for a signal that the process has not installed a handler for (so an unhandled SIGTERM is ignored instead of terminating the process), and PID 1 is expected to reap orphaned child processes. docker stop sends SIGTERM, waits (10 seconds by default, -t changes it) and then sends SIGKILL. Node.js does not install a SIGTERM handler of its own, so an application without one ignores the polite request and is killed after the grace period. The Node.js side of signals is in Process, Signals & Exit Codes.

The shutdown code of the Bookshelf server (src/server.js, excerpt)
let draining = false;

function shutdown(signal) {
  if (draining) return;
  draining = true;
  log.info({ signal }, 'shutting down');
  server.close(() => {
    log.info('closed');
    process.exit(0);
  });
  server.closeIdleConnections();       // do not wait for idle keep-alive sockets
  setTimeout(() => process.exit(1), 8_000).unref();   // hard stop below the orchestrator's grace period
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

Four ways of starting the same image were stopped with docker stop -t 5 and compared. The exit code is 128 plus the signal number, so 143 means the process died of SIGTERM and 137 means SIGKILL.

Table 4. Observed behaviour of docker stop, by how the container starts (Node.js 26)
Entrypoint Time to stop Exit What happened

CMD ["node", "src/server.js"] (handler installed)

0.1 s

0

Node.js is PID 1, receives SIGTERM, runs the shutdown code above and exits cleanly.

CMD ["node", "-e", "setInterval(…​)"] (no handler)

5.1 s (the whole grace period)

137

PID 1 ignored SIGTERM; Docker had to send SIGKILL.

docker run --init with the same no-handler script

0.1 s

143

/sbin/docker-init is PID 1 (observed in /proc/1/cmdline), forwards SIGTERM, and the child dies of the default action.

Shell form sh -c "node src/server.js"

5.1 s

137

The shell stayed PID 1 and did not pass the signal on; the server never logged "shutting down".

Two lessons follow. Always use the exec (JSON array) form of CMD and ENTRYPOINT, so that no shell sits between Docker and Node.js, and install a signal handler for SIGTERM (and SIGINT, for Ctrl-C under docker run -it) in every long-running server. The second lesson is about wrappers. Running npm start as CMD made npm the main process: docker stop ended the server through the process tree, but npm reported a failure (signal SIGTERM, exit 1) and the application’s own handler never logged anything, so its drain code did not run (observed). node --run start as PID 1 behaved like the shell form and ended with exit 137 (observed). Start node directly.

An init process solves the second PID 1 problem too: it reaps zombie processes. A Node.js application that uses child_process can leave terminated children unreaped if Node.js, as PID 1, never waits for processes it did not spawn itself; see Child Processes. Docker bundles tini as docker-init, enabled by docker run --init and by init: true for a Compose service (used below). Docker’s own reference documents the flag.

Run with Docker’s built-in init process
docker run --init --rm -p 3000:3000 bookshelf

An orchestrator such as Kubernetes has no --init flag for a container, so when the image itself must carry an init, install tini in the runtime stage, before USER node (installing packages needs root), and make it the entrypoint. The deps stage is unchanged from the Dockerfile above.

Dockerfile for the Bookshelf server with tini in the image
# syntax=docker/dockerfile:1

ARG NODE_VERSION=26

FROM node:${NODE_VERSION}-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev

FROM node:${NODE_VERSION}-slim AS runtime
RUN apt-get update \
 && apt-get install -y --no-install-recommends tini \
 && rm -rf /var/lib/apt/lists/*
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node package.json ./
COPY --chown=node:node src ./src
USER node
EXPOSE 3000
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "src/server.js"]

Built as bookshelf-tini on Node.js 26 and stopped with docker stop -t 5, the container showed /usr/bin/tini — node src/server.js as PID 1 (/proc/1/cmdline), the server ran as uid 1000 and logged shutting down and closed, the stop took 0.1 s and the exit code was 0: tini forwarded SIGTERM to the server, whose own handler drained and exited.

The official Node.js Docker best-practices document recommends handling signals in the application or using an init; the two are complementary. An application that already handles SIGTERM correctly, as the server above does, does not need tini to shut down; it needs it only to reap children. Between the two the safest default is the handler, because it works the same under Docker, Compose, Kubernetes and a plain terminal. When the process ignores SIGTERM for another reason (for example it is blocked in a synchronous loop, see Don’t Block the Event Loop), neither helps.

Logging follows the same rule as signals: write to stdout and stderr and let the runtime collect them (the pino output above is plain JSON lines that docker logs shows), never to files in the container. See Diagnostic Reports, Logging & Observability.

Listening inside a container

A server bound to 127.0.0.1 listens on the container’s own loopback interface, which -p 3000:3000 cannot reach. The same one-line server was started twice with -p: bound to 127.0.0.1 the request got no reply, bound to 0.0.0.0 it answered. The Bookshelf server therefore binds 0.0.0.0 and reads the port from process.env.PORT; Node.js’s default for listen(port), with no host, is already all interfaces, so the mistake is usually an explicit 'localhost' copied from a development setup. On the host, publish to loopback only (-p 127.0.0.1:3000:3000) when the service should not be reachable from the network. See HTTP Server Fundamentals and Docker networking.

Memory limits and the V8 heap

A container’s memory limit is enforced by the kernel’s cgroup: when the process exceeds it, the kernel kills the process (exit code 137, reported as out-of-memory), without any JavaScript exception. The V8 heap is only one part of a Node.js process’s memory; buffers, native code, the thread pool and the V8 young generation live beside it, so the heap limit has to be set below the container limit. In this experiment a container limited to 256 MiB (--memory 256m) reported a V8 heap limit of 262 MiB on Node.js 26 (259 MiB on 24 and 22): there is no headroom, and a heap that grows to its limit is killed by the kernel before V8 reports a JavaScript heap out-of-memory error. The percentage option or an explicit size fixes that.

Setting the heap relative to the container limit
docker run --rm --memory 256m node:26 \
  node --max-old-space-size-percentage=75 -p "require('v8').getHeapStatistics().heap_size_limit"

# or explicitly, in the image:  ENV NODE_OPTIONS=--max-old-space-size=192

With 75 percent the same container reported 198 MiB on Node.js 26 and 195 MiB on 24 and 22; an explicit --max-old-space-size=192 gave the same numbers (observed). Pick the share by measuring: native memory and buffers count against the container limit too. The Memory & Leaks page explains the heap spaces, and Resources, Logging and Monitoring covers limits from the Docker side.

Compose for local dependencies

During development the application usually runs on the host (with node --watch, see Running Scripts: CLI & REPL) and only its dependencies (database, cache, queue) run in containers; that is what the Bookshelf scratch project does. A Compose file can also run the application next to them to rehearse the container itself, which is what the file below does. The general feature set is in Compose for Local Development and Compose in Practice. The Node.js-specific points are that the application must retry or wait for its dependencies, and that a health check makes Compose wait for them.

compose.yaml with a database, an init process and health checks
services:
  app:
    build:
      context: .
      args:
        NODE_VERSION: "26"
    init: true
    stop_grace_period: 15s
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      DB_HOST: db
      DB_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/healthz').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s

  db:
    image: postgres:17
    environment:
      POSTGRES_DB: bookshelf
      POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d bookshelf"]
      interval: 3s
      timeout: 3s
      retries: 10

volumes:
  pgdata:

The password is not in the file. Compose reads .env next to it, so a local .env containing a throw-away line such as DB_PASSWORD=change-me (listed in both .gitignore and .dockerignore) is enough, and the :? form makes docker compose up fail with a clear message when it is missing instead of starting a database with an empty password. The Compose file format has no version: key any more; it is obsolete and Compose warns about it.

docker compose up -d --build started the database first and the app only after pg_isready passed (depends_on with condition: service_healthy); a few seconds later docker compose ps showed both services healthy. Three more observations from the same run. The container health check has no curl to call, because the slim image does not ship one, so it runs node -e with the built-in fetch instead. Stopping the database made GET /readyz return 503 (the server tries a TCP connection to DB_HOST) and starting it again returned 200, without restarting the app. docker compose stop app took 0.16 s and the exit code was 0, because the handler ran. The project was then removed with docker compose down -v.

Two cautions apply. depends_on only orders startup: the application must still tolerate a dependency that restarts later, by retrying or by reporting not-ready as above. And a database in a volume keeps its first-start password: changing DB_PASSWORD later has no effect until the volume is removed. The database drivers themselves are in Databases from Node.js. For tests that need real databases the Testcontainers approach is described in Testcontainers Fundamentals.

For fast feedback inside the container, Compose can sync edits into a running service with develop.watch, which pairs with node --watch as the command; see the Docker file-watch documentation.

Kubernetes probes and termination

An orchestrator asks the application three questions through probes, and the answers need three different endpoints (or one endpoint with different rules). The mechanics, timing fields and failure behaviour are in Health Probes and Container Lifecycle; this section shows what each means for a Node.js server.

Table 5. What each probe should check in a Node.js application
Probe Question What the Bookshelf server does

startupProbe

Has the process finished starting?

Reuses /healthz. It protects slow starts (loading a large cache, running migrations) from the liveness probe, which only begins after startup succeeds.

livenessProbe

Is the process stuck beyond recovery?

/healthz answers from the event loop and touches no dependency. If a synchronous loop blocks the event loop, the probe times out and the container is restarted. It must not depend on the database: a database outage would then restart every replica for no benefit.

readinessProbe

Should this replica receive traffic now?

/readyz returns 503 while draining and while the database is unreachable, which removes the pod from the Service’s endpoints without restarting it.

Deployment excerpt: probes, resources and a non-root security context (parsed, not applied)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bookshelf
spec:
  replicas: 2
  selector:
    matchLabels: { app: bookshelf }
  template:
    metadata:
      labels: { app: bookshelf }
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: app
          image: registry.example.com/bookshelf:1.0.0
          ports:
            - containerPort: 3000
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits: { memory: 256Mi }
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "5"]
          startupProbe:
            httpGet: { path: /healthz, port: 3000 }
            periodSeconds: 2
            failureThreshold: 15
          livenessProbe:
            httpGet: { path: /healthz, port: 3000 }
            periodSeconds: 10
            timeoutSeconds: 2
          readinessProbe:
            httpGet: { path: /readyz, port: 3000 }
            periodSeconds: 5
            failureThreshold: 2

This manifest was parsed as YAML and checked against the structure above but was not applied, because the experiment had no cluster. When a pod is deleted, the kubelet runs the preStop hook, sends SIGTERM to the container’s main process, and sends SIGKILL after terminationGracePeriodSeconds (30 s here, counted from the start of termination). The removal of the pod from Service endpoints happens in parallel with those steps, so for a moment a load balancer can still send a request to a pod that has begun shutting down. A short preStop sleep and a readiness endpoint that turns 503 on SIGTERM, as in the server above, shrink that window. The application’s own hard-stop timer (8 s here) sits below the grace period, and the grace period sits above preStop plus the longest request you are willing to finish. The same layering for a framework is in Production Deployment.

With the memory limit of 256 Mi above, set the heap as described in Memory limits and the V8 heap. The readOnlyRootFilesystem line works for this server because it writes nothing to disk; an application that needs a scratch directory (uploads, temporary files from File System code) mounts an emptyDir volume there. A manifest that runs the Bookshelf service end to end, with Services, configuration and secrets, is in Bookshelf End to End; configuration and credentials are covered in ConfigMaps and Secrets, and resource requests and limits in Resource Management.

One process per container

Inside a container, scale with replicas, not with a process manager. The cluster module and PM2 solve a problem (using several cores of one machine) that an orchestrator solves one level up by running more containers, each with a CPU request and an independent health check. A container whose PID 1 is a process manager hides crashes from the orchestrator, because the manager restarts the child and the container never exits. Use cluster or worker threads inside one container only when one replica must use several cores for a single workload; see Cluster & Scaling and Worker Threads.

What changed recently

Table 6. Changes that affect Node.js containers, newest first
Version Change

Node.js 26 (LTS from 2026-10-28)

The node:26 images are Debian 13 based when checked; Node.js 26 is the last line on the old six-month cadence, and from Node.js 27 every release becomes LTS, so image tags will track one major per year. Plan the base-image upgrade at the same time as the LTS promotion.

Node.js 25 (EOL 2026-06-01)

Corepack stopped being distributed, so images no longer contain corepack; any Dockerfile that runs corepack enable must install the package manager another way (observed: corepack: not found on node:26-slim).

Node.js 24.10 and 22.21

--env-file and process.loadEnvFile() are no longer experimental in v24.10.0 and v22.21.0, so a Compose env_file or node --env-file=.env replaces dotenv in images.

npm 11 and 12

--production is replaced by --omit=dev. npm 12 (12.2.0 on 2026-10-04) is not bundled with any Node.js release, and blocks dependency install scripts by default, so a Dockerfile that installs a newer npm and relies on install scripts for native add-ons must opt in.

Docker Compose

The version: key is obsolete; depends_on accepts condition: service_healthy, so the shell scripts that polled a database before starting Node.js are not needed.

Where the books are outdated

Both reference books for this section touch containers briefly, and both predate the current baseline. Node.js Projects by Jonathan Wexler (O’Reilly, August 2025) has an appendix with a Dockerfile and Compose setup that targets the Node.js 20 line. Node.js 20 reached end of life on 2026-04-30, so the base image should be node:26-slim (or a 24 or 22 tag) instead, and the image should add what the appendix leaves out: a non-root USER node, npm ci --omit=dev from a lockfile, a multi-stage build and a .dockerignore. Its tooling advice (Corepack for Yarn, nodemon for reload) is superseded: Corepack is no longer bundled since Node.js 25, and node --watch is stable.

Efficient Node.js by Samer Buna (O’Reilly, 2025) is about the runtime itself and treats scaling with cluster, restarts and process managers such as PM2. That advice remains correct on a single machine, but the container model adds a layer it does not discuss: PID 1 signal behaviour, the memory limit and an orchestrator that does the restarting and scaling. Prefer one process per container with the probes and shutdown handling shown above, and treat clustering as a special case described in Cluster & Scaling.

Pitfalls and good practice

  • Do not start the application with the shell form of CMD, with npm start, or with node --run; use CMD ["node", "src/server.js"] (observed: 137 or an npm error instead of a clean exit).

  • Install a SIGTERM handler that stops accepting connections, closes idle sockets and exits, and keep its hard-stop timer below the orchestrator’s grace period.

  • Bind to all interfaces inside the container and decide on the host side, with -p 127.0.0.1:…​, what is exposed.

  • Use npm ci --omit=dev from a lockfile in a build stage; never npm install in the runtime stage.

  • Do not run as root: USER node, plus runAsNonRoot with a numeric runAsUser in Kubernetes.

  • Set the V8 heap below the container memory limit with --max-old-space-size-percentage or an explicit size, and keep other native memory in the budget.

  • Keep secrets out of images, .dockerignore and Compose files: read them from process.env, supplied by an .env file that is not committed, a Kubernetes Secret, or a secret mount. See Crypto, Passwords & Secrets.

  • Make liveness independent of dependencies and readiness dependent on them.

  • Pin the base image major (or digest) and rebuild regularly to pick up OS and Node.js security patches; see Security Best Practices.

  • Log to stdout and stderr, not to files.

Table 7. Where the general container topics are documented
Topic Page

Dockerfile instructions, layers, the cache

Dockerfile Fundamentals, Build Cache and Multi-Stage Builds

Running, networking, storage, debugging containers

Running Containers, Networking, Storage: Volumes and Bind Mounts, Debugging Containers

Image hardening and scanning

Image Best Practices and Supply-Chain Security, Docker Security

Continuous delivery of images

CI/CD with GitHub Actions

Pods, Deployments, rollout strategies

Pods, ReplicaSets and Deployments, Deployment Strategies

Logging and metrics in a cluster

Observability: Logging and Metrics

The same runtime behaviour in a framework

NestJS, whose lifecycle and deployment pages cover shutdown hooks and container images