I read some Docker and Node.js Best Practices articles, e.g. https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md or 10 best practices to containerize Node.js web applications with Docker or Dockerfile good practices for Node and NPM. All these article were written or updated at least in 2021, I don’t list the articles written before 2021 but there quite some.
They are all against CMD ["npm", "run", "start"]
. The main reason is npm will swallow the exit signals such as SIGTERM and SIGINT, so the graceful shutdown code in my node app won’t run.
I guess it was the case for the old npm (although I didn’t test it), but I have tested node14+npm6 and node16+npm8 and I can verify that npm6/8 do NOT swallow those events and my graceful shutdown code is run. Not sure if that was because npm fixed it.
So the only problem remains is there is 1 more process, npm, to run, i.e. NPM run as PID 1. Some articles said the problem with that is "PID 1 will not respond to SIGINT" but as I have verified that is not the case.
Many articles (e.g. this nodejs doc) suggest just CMD [ "node", "server.js" ]
but also in https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals said "Node.js process running as PID 1 will not respond to SIGINT (CTRL-C) and similar signals.", i.e. nodejs own documents contradict themselves (but I do see nodejs as PID 1 responds to SIGINT)
So I am confused with the problem with CMD ["npm", "run", "start"]
or CMD [ "node", "server.js" ]
For my app there is 1 more consideration, my npm scripts has pre hook to make the app run correctly, I have prestart
npm script to make npm start
work. So currently I just use CMD ["npm", "run", "start"]
but I am confused with the "best practice" of how to start my node app in docker.
— update —
I found this closed issue for npm lifecycle: propagate SIGTERM to child
So they did fix it but the latest comment in that issue was in 2017, which said "Yes, this isn’t working, at least with bash; npm runs its lifecycle processes in a shell, and bash doesn’t forward SIGTERM to its children."
I realize I only tested that on my mac and on our CentOS server, and the alpine based docker. It may also because I use exec form, not shell form in CMD so I got the exit signal.
Graceful shutdown with Node.js and Kubernetes said their alpine image didn’t get SIGTERM using npm start
, while I test on alpine3.15 and I can get.
2
Answers
To work around the swallow of SIGINT and SIGTERM, I do this:
where
npm run db:migrate && node ./dist/server/index.js
was the content of mynpm start
with NPM:
check the process graph on docker container
the
npm
process shawns ashell
process, which then spawns thenode
process. This means thatnpm
does not spawn thenode
process as a direct child.causes the
npm
process to fail to pass signals to thenode
process.this is different than how
npm
behaves locally, where it spawns thenode
process directly.with node
process graph
solution:
Or use the
tini
/s6
init system