When my Dockerfile ends with
CMD node .
docker runs that container with the command /bin/sh -c "node ."
instead of simply node .
(I know, I could do that with CMD ["node", "."]
).
I thought that this behavior is actually nice, since it means that inside the container PID1
is /bin/sh
and not my humble node script.
If I understand correctly PID1
is responsible for reaping orphaned zombie processes, and I don't really wan't to be responsible for that... So if /bin/sh
could do that, that would be nice. (I actually thought that this is the reason why docker does rewrite my CMD
).
The problem is that when I send a SIGTERM
to the container (started with /bin/sh -c "node ."
), either via docker-composer stop
or docker-composer kill -s SIGTERM
, the signal doesn't reach my node
process and therefore it get's forcefully killed everytime with a SIGKILL
after the 10 seconds grace period. Not nice.
Is there a way to have someone manage my zombies and have my node instance receive the signals sent by docker?
The docker stop command attempts to stop a running container first by sending a SIGTERM signal to the root process (PID 1) in the container. If the process hasn't exited within the timeout period a SIGKILL signal will be sent.
The docker stop commands issue the SIGTERM signal, whereas the docker kill commands sends the SIGKILL signal. The execution of the SIGTERM and SIGKILL. is different. Unlike SIGKILL, the SIGTERM gracefully terminates a process rather than killing it immediately.
I think you have to understand the roles of ENTRYPOINT
and CMD
, and use the ENTRYPOINT
(exec form) way in your Dockerfile
.
ENTRYPOINT
, which specifies the starting executable of the container, is the core part of a Docker container. Every container MUST have an entrypoint to decide where to start. By default the value is /bin/bash -c
. In addition, everything set by CMD
would be appended to ENTRYPOINT
as arguments.
Therefore, if you failed to specify ENTRYPOINT
in your Dockerfile
, the actual entrypoint would be /bin/bash -c {your_command_in_CMD}
, which unfortunately DOES NOT pass signals.
ENTRYPOINT
have two forms: exec form and shell form
As the Docker reference pointed out: exec form is recommended, and shell form has the disadvantage that command is executed by /bin/bash -c
, which might not work well with signals:
The shell form prevents any
CMD
orrun
command line arguments from being used, but has the disadvantage that yourENTRYPOINT
will be started as a subcommand of/bin/sh -c
, which does not pass signals. This means that the executable will not be the container’sPID 1
- and will not receive Unix signals - so your executable will not receive aSIGTERM
fromdocker stop <container>
.
There are tools designed to solve this problem:
I think if you only have a single process, all you need to do is explicitly handle the signal with a signal handler, which bash doesn't do for you.
Using the ["node", "."]
syntax, you could use https://nodejs.org/api/process.html#process_signal_events and just have it exit on SIGTERM. I believe that would be enough.
Or using a bash script you can use trap "exit 0" TERM
You could also use a process supervisor like http://skarnet.org/software/s6/
My solution for the PID1 problem:
The Dockerfile ends with:
ENTRYPOINT ["/bin/bash", "-c"]
Or leave ENTRYPOINT completely out of your Dockerfile. The default is /bin/sh -c
.
Than I run with this shell script, which has the filename of node
and have chmod +x
:
#!/bin/bash
docker run --rm -it -p 8083:80 -v $HOME/node/work/:/root/node/:rw node_node "echo pid1 > /dev/null && node $@"
The trick is "echo pid1 > /dev/null && node $@"
which is the command. $@
is a shell script to accept user input from command line.
echo
will trap PID1 and sending output to /dev/null
.
For example ./node -v
will return the version of Node.js inside the running container.
Running a webserver with ./node /root/node/hello-world.js
command, CTRL+C will work again.
Here is my Dockerised Node.js development environment.
EDIT:
Totally crazy idea, but add this node
named bash script to the $PATH. So on the host, you can type node -v
instead of ./node -v
. And you run node in a Docker container which is totally looks like it was installed on the host. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With