Node.js process management in Docker
There is one issue with Node
when it is started by the npm
process – it does not respond to termination signals by default. This may be not the bast way to terminate containers. In this post I will show you how to handle Node
process in Docker in a proper way.
PID 1 problem
PID 1
is the first process in a operating system (or container) known also as init
process. It has two jobs:
- handle zombie processes – take care of sub-processes which parent crashed or has been killed and the sub-process is hanging, thus cannot be terminated properly by non-existing parent process
- pass signals to sub-processes – this is important when you want to shut down a process
Proper Node shutdown
Docker uses Linux signals to stop container:
SIGINT
– send when you useCTRL + C
SIGTERM
– send when you typedocker container stop
or doing rolling updateSIGKILL
– immediately kill the container, without sending information to it what is going to happen. It is not desirable move, as container won’t be given a chance to respond to it. So it can be killed in the middle of some data processing or anything.
Hence SIGINT
and SIGTERM
signals are sent to container, it allows to terminate by itself – container has some time to stop it’s processes before it terminates, eg. close connections to database, save files, clear memory, etc.
So as you can imagine, if possible, we want to use those two signals to terminate containers. Unfortunately, npm
process does not respond to SIGINT/SIGTERM
. What is more ,node
process itself also does not, but fortunately, we can add some additional configuration to make it work as we want, eg. respect SIGINT/SIGTERM
to make graceful shutdowns possible.
If you have an application in the container and you do something like actrl + c
ordocker container stop
, and it seems to wait about 10 seconds - that's because Docker is not able to properly shut it down withSIGTERM
andSIGINT
, so it's forced to doSIGKILL
. It is because Docker - by default - will wait 10 seconds for the app to respond, and if it is not responding - it will just kill it.
Graceful shutdown options
When using Node in Docker – as long as we’re launching Node directly inside the container – we have three options to run in properly. They are in order from least to most preference.
Tini via cli – temporary workaround
Use --init
flag while starting the container to enable it to respond to termination signals. Remember, it only makes ctrl-c
or docker container stop
work – it does not gracefully shut down any connections inside container.
Tini in Dockerfile – permanent workaround
More details in github repo.
# Add Tini ENV TINI_VERSION v0.19.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini ENTRYPOINT ["/tini", "--"]
Result is the same as putting --init
in cli. The only difference is, you do not have to remember about it when starting a container. This solution still does not handle graceful shutdown.
Add script to handle container termination
Paste below code somewhere in your app – just be sure it will be executed every time when application starts. As mentioned before, Node
does not handle SIGINT/SIGTERM
signals by default, so we have to implement it.
// CTRL+C process.on('SIGINT', function onSigint() { // do all the cleanup, close connections, etc }) // docker stop process.on('SIGTERM', function onSigterm() { // do all the cleanup, close connections, etc })
You are allowed to terminate and handle all of the stuff happening in the application, before it gets terminated.