I'm having trouble understanding the startup commands for the services in this docker-compose.yml. The two relevant lines from the .yml are:
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
and
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
Why send the sleep
command to the background and then wait on it? Why not just do sleep 6h
directly? Also, is the double dollar sign just escaping the dollar sign in ${!}
?
I'm finding other places where sleep and wait are used in conjunction, but none seem to have any explanation of why:
It makes sense to sleep in background and then wait, when one wants to handle signals in a timely manner.
(detailed explanation here).
While the second example implements a signal handler, for the first one it makes no difference whether the sleep is executed in foreground or not. There is no trap and the signal is not propagated to the
nginx
process. To make it respond to theSIGTERM
signal, the entrypoint should be something this:To test it:
Stop the container
or send the
TERM
signal (docker stop
sends aTERM
followed byKILL
if the main process does not exit)By doing this, the scripts exits immediately. Now if we remove the
wait ${!}
the trap is executed whensleep
ends. All that works well for the second example too.Note: in both cases the intention is to check certificate renewal every 12h and reload the configuration every 6h as mentioned in the guide The two commands do that just fine. IMHO the additional wait in the first example is just an oversight of the developers.
EDITED:
It seems the rationalization above, which was meant to give possible reasons behind the background sleep, might create some confusion. (There is a related post Why use nginx with “daemon off” in background with docker?).
While the command suggested in the answer above is an improvement over the one in the question it is still flawed because, as mentioned in the linked post, the
nginx
server should be the main process and not a child. That can be easily achieved using theexec
system call. The script becomes:(More info in section Configure app as PID 1 in Docker best practices)
This, IMHO, is far better because not only is
nginx
monitored but it also handle signals. A configuration reload (nginx -s reload
), for example, can also be done manually by simply sending theHUP
signal to the docker container (See Controlling nginx).The only reason I see:
If you
killall -INT sleep
, this won't affect main script.Try this:
Then send a
Interrupt
signal:This will break the job!
Try now
Then again:
Job won't break!
Sample output, hitting
killall -INT sleep
from another window: