Methods
If you are starting your dockerized application from an ENTRYPOINT or a CMD you might have noticed that the application simply gets killed after 10 seconds of issuing a docker stop
command on the container. This happens because your application does not get a chance to process the SIGTERM signal issued by docker.
There are two reasons this could be happening.
- You application is getting the signal but it does not know how to process it.
- You application knows how to process the signal, but never gets it.
In the first case you have two options:
-
Modify your application to handle the SIGTERM. For example a node server would do something like this:
process.on('SIGTERM', (signal) => { serverEx.close(() => { process.exit(128) }) })
-
Have the shell wrapper handle SIGTERM on behalf of the application. More on how to do this later.
In the second case the fix it to make sure your ENTRYPOINT or CMD script forward the signals correctly. There are also two options
-
Replace your script with the application process by invoking it via
exec application
bash command. Docker sends signals to PID 1. Afterexec
the application will be running as PID 1 and will get the signal -
Catch the signal in the script and re-send it to the application using
trap 'kill -SIGTERM $(pidof app)' SIGTERM
. This has an advantage of processing the signal inside the script before or after sending it to the application.
Using the shell wrapper to handle docker signals
This is useful when you want to issue additional commands to the application before stopping it. To do this you might need the application to run in the background while still getting STDIN from the script.
-
First create a named pipe to route STDIN through:
mkfifo /data/in
. -
Then block it for writing, so it does not get closed when your process read all of the current contents:
sleep infinity > /data/in &
. Sleeping forever is better thantailf -f /dev/null
because tailf uses inotify resources and will be triggered each time some app sends data to /dev/null. You can see this by runningstrace
on it. It is also better thancat > /dev/null &
because inside of dockercat
will be itself disconnected from STDIN, which in turn will close/data/in
. -
Start your process in the background with the
/data/in
providing STDIN:application < /data/in &
. This works better than using piping like thistail -f /data/in | application &
because the pipe will only get terminated if the tail stops, but if your application crashes the pipe will keep running. -
Halt waiting for the application to finish.
wait $(pidof application)
. This will ensure that the docker container is running as long as the applicaiton is running. If the application crashed, the container started with-d --restart unless-stopped
will be automatically restarted. -
Handle your signals. Somewhere before the
wait
command put in the signal handling routines:
trap 'server_shutdown' SIGTERM
function server_shutdown() {
pid=$(pidof applicaiton)
if [ -z "$pid" ]; then echo "Already stopped"; exit 1; fi
echo "stop" > /proc/$pid/fd/0
# Wait for it to finish saving and exit
while [ -e /proc/$pid ]; do
sleep 1
done
echo "done"
exit 0
}
Note: an alternative way to the named pipe that’s held open may be bash v4 coprocesses using the ‘coproc’ command. A coprocess has a two-way pipe established between the executing shell and runs in the background.