docker, javascript, linux, nodejs

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 use CTRL + C
  • SIGTERM – send when you type docker container stop or doing rolling update
  • SIGKILL – 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 a ctrl + c or docker container stop, and it seems to wait about 10 seconds - that's because Docker is not able to properly shut it down with SIGTERM and SIGINT, so it's forced to do SIGKILL. 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.