recipes / run-node-directly
Run node directly, not npm start
Make node PID 1 so Kubernetes signals reach your application code.
Kubernetes stops a Pod by sending SIGTERM to PID 1 of each container. If PID 1 is
npm, a shell, or anything other than your app, the signal may be swallowed or delayed,
and after terminationGracePeriodSeconds the container is killed with SIGKILL.
Do this
CMD ["node", "server.js"]
Use the exec form (JSON array). node becomes PID 1 and receives signals directly.
Not this
# npm is PID 1 and spawns node as a child
CMD ["npm", "start"]
# shell form: /bin/sh -c "node server.js" makes sh PID 1
CMD node server.js
Newer npm versions forward signals, but they add a process, add startup time,
and hide the exit code. A shell in front does not forward SIGTERM at all.
Notes
- Need
npm startfor local development? Keep it inpackage.json, just do not use it in the image. - Node as PID 1 does not reap zombie processes. If your app spawns child processes,
run with an init process:
docker run --init, ortiniasENTRYPOINT. - Distroless Node images already set
ENTRYPOINT ["node"], soCMD ["server.js"]is enough. - Environment variables in the exec form are not expanded. Read them in code, not in
CMD.
See it applied
- examples/full/Dockerfile in the repository
- examples/full - every recipe applied to one app
Updated 2026-09-10 · tags: dockerfile, signals, pid1 · edit on GitHub