0

I'm containerizing a PHP application and would like to modify the Apache configuration based on environment variables. This is done in a script overriding the default ENTRYPOINT:

FROM php:7.2-apache
# ...
COPY prepare-docker-configs.sh .
RUN chmod +x prepare-docker-configs.sh
ENTRYPOINT ./prepare-docker-configs.sh

After those modifications, Apache doesn't start. apache2-foreground seems to be the missing command, so I run it at the end of prepare-docker-configs.sh

#!/bin/bash
# ... Some config substitution
apache2-foreground

Now Apache got started and everything works as expected. But I noticed that stopping the container is much slower than before. I ran time docker-compose down for both constellations:

Without my custom overriden ENTRYPOINT

real    0m2,080s
user    0m0,449s
sys 0m0,064

With my custom script as ENTRYPOINT

real    0m12,247s
user    0m0,491s
sys 0m0,067s

So it takes about 10 seconds longer. Especially during development where a lot of testing is done, this would waist a lot of time in total.

Why is my custom ENTRYPOINT so much slower on stopping and how could it be fixed?

I tried adding STOPSIGNAL SIGWINCH from the original Dockerfile and also run docker-php-entrypoint, both doesn't help.

The docker-compose.yml file is nothing special. It just defines the services and override the default network because of internal conflicts:

version: '2' 
services: 
  app: 
    build:
      context: .
      args:
        http_proxy: ${http_proxy}
    env_file: docker.env
    ports:
      - 80:80
      
networks:
  default:
    driver: bridge
    ipam:
      config:
       - subnet: 10.10.8.0/28

What doesn't work

Ressource issues

I'm running this on my Ubuntu workstation with SSD, i7 Quadcore and 32GB RAM. It doesn't run anything large, the load is quite low. A ressource issue is very unlikely. And the performance issue is reproduceable: On another Ubuntu machine with Ryzen 5 3600 and 48GB memory it took 11 seconds with overriden ENTRYPOINT. The same result on Debian with a much slower i3.

Calling the original ENTRYPOINT

In my script, I call docker-php-entrypoint at the end, which executes the original entrypoint script from the PHP image. It doesn't start Apache successfully, I had to call apache2-foreground instead.

Starting the Apache process with the RUN statement

I added a CMD directive to my Dockerfile

ENTRYPOINT ./prepare-docker-configs.sh
CMD apache2-foreground

and an exec statement at the end of prepare-docker-configs.sh assuming that the CMD entry got passed

set -x
exec "$@"

But the container exited because nothing was passed

app_1     | + set -x
app_1     | + exec
test_app_1 exited with code 0

I tested passing the file directly

exec apache2-foreground

Now Apache is started, but it still takes 10+ seconds to stop.

Lion
  • 16,606
  • 23
  • 86
  • 148

1 Answers1

5

A Docker container runs a single process; when you declare an ENTRYPOINT in the Dockerfile, that is the process. When you docker stop a container, it sends SIGTERM to that process (only), and if it doesn't stop itself within 10 seconds, it sends SIGKILL to forcibly kill it off. Since the container process has process ID 1, there are also some special conditions on signal handling.

In your case, the bash instance running the entrypoint script is the root process, and it's running apache2-foreground as a child process. You can use the Bourne shell exec command to replace the shell with the process you're trying to run; then apache2-foreground the main container process instead, and the docker stop signals go straight to that process.

A typical pattern for entrypoint scripts is to honor the Docker "command" part. This gets passed to the entrypoint as additional arguments, so your entrypoint script typically looks like

#!/bin/sh
# ... Some config substitution
exec "$@"

and then in your Dockerfile you need to provide the default command to run

# NOTE! ENTRYPOINT must be JSON-array syntax for this to work
ENTRYPOINT ["./prepare-docker-configs.sh"]
CMD apache2-foreground

Since the entrypoint script still execs the command, it replaces the shell command wrapper as process 1 and will receive the docker stop signals.

David Maze
  • 130,717
  • 29
  • 175
  • 215
  • +1 this is exactly the cause. You can see here that the default apache httpd container (maybe this is the one Lion is overriding?) uses exec as described in this answer: https://github.com/docker-library/httpd/blob/master/2.4/httpd-foreground#L7 – Dave Cameron Jun 26 '20 at 14:19
  • I saw something similar in [the php entrypoint](https://github.com/docker-library/php/blob/a041c4250ba98a53264a452a907288a40d94aa79/7.2/buster/apache/docker-php-entrypoint) and tried that. But stopping is still slow as before, altough your explanation seems logical. I edited my question with more details what exactly I have tried. – Lion Jun 26 '20 at 14:33
  • @DaveCameron Yes, `docker-php-entrypoint` is the original one I'm overriding. It is defined in the original Dockerfile here: https://github.com/docker-library/php/blob/a041c4250ba98a53264a452a907288a40d94aa79/7.2/buster/apache/Dockerfile#L272 – Lion Jun 26 '20 at 14:35
  • 1
    The `ENTRYPOINT` must use the JSON-array syntax. (Otherwise Docker inserts a `sh -c` wrapper, which swallows the `CMD` arguments.) – David Maze Jun 26 '20 at 15:09
  • @DavidMaze Omg, I changed both to the JSON syntax `CMD ["apache2-foreground"]` and `ENTRYPOINT ["./prepare-docker-configs.sh"]`, now it took just 2 seconds to stop! Thanks for pointing this out – Lion Jun 30 '20 at 07:45
  • Nice ! the json syntax on the CMD is very important too else it doesn't work. @DavidMaze can you update your answer please ? – John May 02 '22 at 08:14