It all started from this article about setting up nginx and certbot in docker. In the end of the manual the author made the automatic certificate renewal for nginx with this command:
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
I'm not the only one who didn't understand this part, so there was a question on SO: Why do sleep & wait in bash?
The answer was given that the original command was not perfect and here is the corrected version:
/bin/sh -c 'nginx -g \"daemon off;\" & trap exit TERM; while :; do sleep 6h & wait $${!}; nginx -s reload; done'
But in this command I see nginx -g \"daemon off;\" &
Why do we first put nginx on foreground and then stuff it in background? What are implications and why not just launch nginx in background at first?
Another question: as I understand, the while
cycle stays in foreground for docker, unlike the original command. But if nginx if background, does it mean that if it dies, docker does not care? In foreground while
is still working, no problem.
And the last question: why in this commands sometimes we see $${!}
and sometimes ${!}
. Example of ${!}
from the same SO question:
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'
I know it's a character escaping, but I don't figure out the rules for this case.
The reason was mainly to highlight the differences and there are no implications. The command is equivalent to:
The command basically creates three processes: the shell process (
/bin/sh
),sleep 6H
and thenginx
server. A fourth process (nginx -s reload
) is forked every 6 hours. Docker always monitors the process withPID 1
which in this case is the shell (/bin/sh
). If the shell dies the container exits. If thenginx
server, which is a child of the shell process, dies docker, indeed doesn't care.The "corrected" version doesn't address these issues. It has the same problems as the original one. The answer to the SO question only highlights that the
sleep
andwait
is not needed unless you want to handle signals in a timely manner. It means that:does exactly the same thing as:
In conclusion, a proper implementation would have
nginx
as the main process (PID 1
) and another process running in background waking up every 6h to signal the server to reload the configuration. Neither the original, nor the corrected command implement all this properly.To fix the before mentioned problems the command should be like this:
The
exec
system call replaces the content of the shell process with thenginx
server makingnginx
the main process in foreground. All the signals are now propagated correctly to the server (see also Controlling nginx).Note: This solution still has a flaw. The shell process (the while loop) is not monitored. If for any reason this process exits the only thing docker does is to send an alert.
Hope this sheds some light.
Answer to my last question regarding
${!}
and$${!}
: Apparently, if we write a command in docker-compose file with one dollar sign (${!}
), it will be expanded by docker-compose into 'pid of last background command relative to shell that launched docker-compose'. So, the entrypoint in container will look like this:With
$${!}
the dollar sign is escaped in docker-compose processing. The entrypoint in container will be something likeSource: https://stackoverflow.com/a/40621373/11931043