One of the things they mention in Express docs is to add support for graceful shutdown of your application:
The process manager you’re using will first send a SIGTERM signal to the application to notify it that it will be killed. Once the application gets this signal, it should stop accepting new requests, finish all the ongoing requests, clean up the resources it used, including database connections and file locks then exit.
The Express docs also point to the http-terminator library for adding graceful shutdown to your applications. Their code sample is in TypeScript but you can use the library from JavaScript like this:
const { createHttpTerminator } = require('http-terminator');
const httpTerminator = createHttpTerminator({ server: httpsServer });
process.on('SIGTERM', async () => {
debug('SIGTERM signal received: closing HTTP server')
await httpTerminator.terminate();
});
I tried this but nothing happened when I stopped the container running my Node.js app using docker stop. The callback never gets invoked.
What’s happening?
What’s going on here is that the entrypoint or CMD of my docker container was a shell script called run-server.sh. And within that shell script I had following line of code that actually runs the node program:
node server.js
Now the way Bash shell works is that it spawns a child process to run node and signals sent to the parent process are not forwarded to the child processes – the cause of the problem. The child process can be seen here:
bash-5.0# ps aux
PID USER TIME COMMAND
1 root 0:00 bash ./run-server.sh
18 root 0:00 node /usr/local/bin/npx nodemon main.js
30 root 0:00 /usr/local/bin/node main.js
41 root 0:00 /bin/bash
46 root 0:00 ps aux
Fortunately there is an easy way to fix this and is to use the exec command provided by Bash.
The exec() family of functions replaces the current process image
with a new process image.
So in our run-server.sh we simply make following change:
exec node server.js
Now Bash will NOT spawn a child process to execute node and the SIGTERM sent by docker stop will make its way to the Node application.
There is one catch to this solution which is that your node command must be the last command in your shell script. This would likely be the case since the node command will run forever listening for requests from clients.
References: