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:
- http://www.masteringunixshell.net/qa17/bash-how-to-wait-seconds.html
- https://stackoverflow.com/a/13301329/828584
- https://superuser.com/a/753984/98583
It makes sense to sleep in background and then wait, when one wants to handle signals in a timely manner.
When bash is executing an external command in the foreground, it does
not handle any signals received until the foreground process
terminates
(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 the SIGTERM
signal, the entrypoint should be something this:
/bin/sh -c 'nginx -g \"daemon off;\" & trap exit TERM; while :; do sleep 6h & wait $${!}; nginx -s reload; done'
To test it:
docker run --name test --rm --entrypoint="/bin/sh" nginx -c 'nginx -g "daemon off;" & trap exit TERM; while :; do sleep 20 & wait ${!}; echo running; done'
Stop the container
docker stop test
or send the TERM
signal (docker stop
sends a TERM
followed by KILL
if the main process does not exit)
docker kill --signal=SIGTERM test
By doing this, the scripts exits immediately. Now if we remove the wait ${!}
the trap is executed when sleep
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 the exec
system call. The script becomes:
'while :; do sleep 6h; nginx -s reload; done & exec nginx -g "daemon off;"'
(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 the HUP
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:
while true ;do sleep 12; echo yes;done
Then send a Interrupt
signal:
killall -INT sleep
This will break the job!
Try now
while true ;do sleep 12 & wait $! ; echo yes;done
Then again:
killall -INT sleep
Job won't break!
Sample output, hitting killall -INT sleep
from another window:
user@myhost:~$ while true ;do sleep 12; echo yes;done
break
user@myhost:~$ while true ;do sleep 12 & wait $! ; echo yes;done
[1] 30632
[1]+ Interrupt sleep 12
yes
[1] 30636
[1]+ Interrupt sleep 12
yes
[1] 30638
[1]+ Interrupt sleep 12
yes
[1] 30640