diff --git a/CHANGELOG.md b/CHANGELOG.md index f7837ac..3b5a083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ### Minor changes +* **2026-10-09** + * Fix the fpm variant stop: PHP-FPM is now stopped gracefully (`SIGQUIT`: running requests are completed) and port 9000 is exposed + * Fix stop requests sent while the apache or fpm container is starting: the stop signal (`SIGWINCH` / `SIGQUIT`) was ignored by the entrypoint and the container was killed after the stop timeout + * Fix Apache modules listed in the documentation but rejected by `APACHE_EXTENSION_*`: `brotli`, `cern_meta`, `imagemap`, `md`, `proxy_hcheck`, `proxy_uwsgi`, `socache_redis` + * Images built from a slim image are ~105MB lighter: the apt binary caches (`/var/cache/apt/*.bin`) were left by the ONBUILD hook (even without any extension), and the hook no longer runs apt when `PHP_EXTENSIONS` is empty + * Fat images are ~105MB lighter (same apt binary caches left after installing the extensions) + * **2026-10-06** * Fix ev and event extensions on PHP 7.4 (ev pinned to 1.1.5, event dependencies updated for Ubuntu 24.04) diff --git a/Dockerfile.slim.fpm b/Dockerfile.slim.fpm index 99f9299..7b454d7 100644 --- a/Dockerfile.slim.fpm +++ b/Dockerfile.slim.fpm @@ -174,6 +174,10 @@ RUN rm /etc/php/${PHP_VERSION}/fpm/php.ini && \ COPY utils/fpm-docker.conf /etc/php/${PHP_VERSION}/fpm/pool.d/docker.conf COPY utils/fpm-zz-docker.conf /etc/php/${PHP_VERSION}/fpm/pool.d/zz-docker.conf +EXPOSE 9000 + +STOPSIGNAL SIGQUIT + @@ -273,7 +277,7 @@ RUN touch /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini && ln -s /et RUN ln -s /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini /etc/php/${PHP_VERSION}/fpm/conf.d/generated_conf.ini && \ sed -i 's/^user = www-data/;user = www-data/g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ sed -i 's/^group = www-data/;group = www-data/g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ - sed -i 's#listen = /run/php/php${PHP_VERSION}-fpm.sock#;listen = /run/php/php${PHP_VERSION}-fpm.sock#g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ + sed -i "s#^listen = /run/php/php${PHP_VERSION}-fpm.sock#;listen = /run/php/php${PHP_VERSION}-fpm.sock#g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ sed -i "s#pid = /run/php/php${PHP_VERSION}-fpm.pid#;pid = /run/php/php${PHP_VERSION}-fpm.pid#g" /etc/php/${PHP_VERSION}/fpm/php-fpm.conf diff --git a/extensions/core/install_all.sh b/extensions/core/install_all.sh index 19cfa54..29bd096 100755 --- a/extensions/core/install_all.sh +++ b/extensions/core/install_all.sh @@ -40,5 +40,5 @@ mv /tmp/docker-clean /etc/apt/apt.conf.d/docker-clean apt purge -y php-pear build-essential php${PHP_VERSION}-dev pkg-config apt autoremove -y apt clean -rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* +rm -rf /var/lib/apt/lists/* /var/cache/apt/*.bin /tmp/* /var/tmp/* /usr/share/doc/* rm -f /usr/local/bin/pickle diff --git a/tests-suite/variant-apache.sh b/tests-suite/variant-apache.sh index 3555b14..3cd6175 100755 --- a/tests-suite/variant-apache.sh +++ b/tests-suite/variant-apache.sh @@ -41,7 +41,32 @@ test_changeMemoryLimit() { assert_equals "2G" "$RESULT" "Apache PHP_INI_MEMORY_LIMIT was not applied" } +############################################################ +## Apache modules can be enabled with APACHE_EXTENSION_* +############################################################ +test_enableApacheModule() { + RESULT="$(docker run ${RUN_OPTIONS} --rm -e APACHE_EXTENSION_BROTLI=1 \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" ls /etc/apache2/mods-enabled 2>&1)" + assert_matches "brotli.load" "$RESULT" "APACHE_EXTENSION_BROTLI was not applied" + assert_matches "php${PHP_VERSION}.load" "$RESULT" "mod_php should stay enabled" + assert_not_matches "does not exist" "$RESULT" "a2enmod/a2dismod received an unknown module" +} +############################################################ +## A stop requested during the initialization is not lost +############################################################ +test_stopDuringStartup() { + docker run --name "${STOP_NAME}" ${RUN_OPTIONS} -d \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" > /dev/null + sleep 0.3 + START=$(date +%s) + docker stop "${STOP_NAME}" > /dev/null 2>&1 + DURATION=$(( $(date +%s) - START )) + docker rm -f "${STOP_NAME}" > /dev/null 2>&1 + assert "test ${DURATION} -lt 5" "Stopping during startup took ${DURATION}s (killed by timeout?)" +} + setup_suite() { + export STOP_NAME="test-apache-stop-$(unused_port)" # SETUP apache1 export DOCKER1_PORT="$(unused_port)" export DOCKER1_NAME="test-apache1-${DOCKER1_PORT}" @@ -68,4 +93,5 @@ setup_suite() { teardown_suite() { docker stop "${DOCKER1_NAME}" "${DOCKER2_NAME}" "${DOCKER3_NAME}" > /dev/null 2>&1 + docker rm -f "${STOP_NAME}" > /dev/null 2>&1 } diff --git a/tests-suite/variant-fpm.sh b/tests-suite/variant-fpm.sh index 6fc5e11..8782ca3 100755 --- a/tests-suite/variant-fpm.sh +++ b/tests-suite/variant-fpm.sh @@ -19,10 +19,45 @@ test_start() { assert_equals "0" "$?" "Docker stop failed" } +############################################################ +## Graceful stop (SIGQUIT): fast and successful exit +############################################################ +test_gracefulStop() { + docker run --name "${FPM_STOP_CONTAINER_NAME}" ${RUN_OPTIONS} -d \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" > /dev/null + # Let's wait for FPM to start + for _ in $(seq 1 60); do + docker logs "${FPM_STOP_CONTAINER_NAME}" 2>&1 | grep -q "ready to handle connections" && break + sleep 0.5 + done + START=$(date +%s) + docker stop "${FPM_STOP_CONTAINER_NAME}" > /dev/null 2>&1 + DURATION=$(( $(date +%s) - START )) + EXIT_CODE="$(docker inspect -f '{{.State.ExitCode}}' "${FPM_STOP_CONTAINER_NAME}")" + docker rm "${FPM_STOP_CONTAINER_NAME}" > /dev/null 2>&1 + assert_equals "0" "$EXIT_CODE" "PHP-FPM did not stop gracefully" + assert "test ${DURATION} -lt 5" "PHP-FPM took ${DURATION}s to stop (killed by timeout?)" +} +############################################################ +## A stop requested during the initialization is not lost +############################################################ +test_stopDuringStartup() { + docker run --name "${FPM_STOP_CONTAINER_NAME}" ${RUN_OPTIONS} -d \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" > /dev/null + sleep 0.3 + START=$(date +%s) + docker stop "${FPM_STOP_CONTAINER_NAME}" > /dev/null 2>&1 + DURATION=$(( $(date +%s) - START )) + docker rm -f "${FPM_STOP_CONTAINER_NAME}" > /dev/null 2>&1 + assert "test ${DURATION} -lt 5" "Stopping during startup took ${DURATION}s (killed by timeout?)" +} + setup_suite() { export FPM_CONTAINER_NAME="test-fpm-$(unused_port)" + export FPM_STOP_CONTAINER_NAME="test-fpm-stop-$(unused_port)" } teardown_suite() { docker stop "${FPM_CONTAINER_NAME}" > /dev/null 2>&1 + docker rm -f "${FPM_STOP_CONTAINER_NAME}" > /dev/null 2>&1 } diff --git a/utils/Dockerfile.slim.blueprint b/utils/Dockerfile.slim.blueprint index 4cf8fa0..0a70de9 100644 --- a/utils/Dockerfile.slim.blueprint +++ b/utils/Dockerfile.slim.blueprint @@ -246,6 +246,10 @@ RUN rm /etc/php/${PHP_VERSION}/fpm/php.ini && \ ln -s /usr/sbin/php-fpm${PHP_VERSION} /usr/sbin/php-fpm COPY utils/fpm-docker.conf /etc/php/${PHP_VERSION}/fpm/pool.d/docker.conf COPY utils/fpm-zz-docker.conf /etc/php/${PHP_VERSION}/fpm/pool.d/zz-docker.conf + +EXPOSE 9000 + +STOPSIGNAL SIGQUIT {{end}} @@ -374,7 +378,7 @@ RUN ln -s /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini /etc/php/${P RUN ln -s /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini /etc/php/${PHP_VERSION}/fpm/conf.d/generated_conf.ini && \ sed -i 's/^user = www-data/;user = www-data/g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ sed -i 's/^group = www-data/;group = www-data/g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ - sed -i 's#listen = /run/php/php${PHP_VERSION}-fpm.sock#;listen = /run/php/php${PHP_VERSION}-fpm.sock#g' /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ + sed -i "s#^listen = /run/php/php${PHP_VERSION}-fpm.sock#;listen = /run/php/php${PHP_VERSION}-fpm.sock#g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf && \ sed -i "s#pid = /run/php/php${PHP_VERSION}-fpm.pid#;pid = /run/php/php${PHP_VERSION}-fpm.pid#g" /etc/php/${PHP_VERSION}/fpm/php-fpm.conf {{end}} diff --git a/utils/apache2-foreground b/utils/apache2-foreground index 5fe22e2..5cca336 100755 --- a/utils/apache2-foreground +++ b/utils/apache2-foreground @@ -1,6 +1,9 @@ #!/bin/bash set -e +# Stop right away if a graceful stop is requested before Apache is started +trap 'exit 0' WINCH + # Note: we don't just use "apache2ctl" here because it itself is just a shell-script wrapper around apache2 which provides extra functionality like "apache2ctl start" for launching apache2 in the background. # (also, when run as "apache2ctl ", it does not use "exec", which leaves an undesirable resident shell process) diff --git a/utils/docker-entrypoint-as-root.sh b/utils/docker-entrypoint-as-root.sh index 70f2eb8..d2e3b13 100755 --- a/utils/docker-entrypoint-as-root.sh +++ b/utils/docker-entrypoint-as-root.sh @@ -2,6 +2,12 @@ set -e +# Stop right away if a graceful stop is requested during the initialization +trap 'exit 0' QUIT +if [[ "$IMAGE_VARIANT" == "apache" ]]; then + trap 'exit 0' WINCH +fi + # Let's write a file saying the container is started (we are no longer in build mode, useful for php_proxy.sh) touch /opt/container_started diff --git a/utils/enable_apache_mods.php b/utils/enable_apache_mods.php index 513b00a..f921b3a 100644 --- a/utils/enable_apache_mods.php +++ b/utils/enable_apache_mods.php @@ -5,7 +5,7 @@ $defaultExtensions = ['access_compat', 'alias', 'auth_basic', 'authn_core', 'authn_file', 'authz_core', 'authz_host', 'authz_user', 'autoindex', 'deflate', 'dir', 'env', 'expires', 'filter', 'mime', 'mpm_prefork', 'negotiation', 'php'.getenv('PHP_VERSION'), 'reqtimeout', 'rewrite', 'setenvif', 'status']; -$availableExtensions = ['access_compat', 'actions', 'alias', 'allowmethods', 'asis', 'auth_basic', 'auth_digest', 'auth_form', 'authn_anon', 'authn_core', 'authn_dbd', 'authn_dbm', 'authn_file', 'authn_socache', 'authnz_fcgi', 'authnz_ldap', 'authz_core', 'authz_dbd', 'authz_dbm', 'authz_groupfile', 'authz_host', 'authz_owner', 'authz_user', 'autoindex', 'buffer', 'cache', 'cache_disk', 'cache_socache', 'cgi', 'cgid', 'charset_lite', 'data', 'dav', 'dav_fs', 'dav_lock', 'dbd', 'deflate', 'dialup', 'dir', 'dump_io', 'echo', 'env', 'ext_filter', 'expires', 'file_cache', 'filter', 'headers', 'heartbeat', 'heartmonitor', 'ident', 'include', 'info', 'lbmethod_bybusyness', 'lbmethod_byrequests', 'lbmethod_bytraffic', 'lbmethod_heartbeat', 'ldap', 'log_debug', 'log_forensic', 'lua', 'macro', 'mime', 'mime_magic', 'mpm_event', 'mpm_prefork', 'mpm_worker', 'negotiation', 'php'.getenv('PHP_VERSION'), 'proxy', 'proxy_ajp', 'proxy_balancer', 'proxy_connect', 'proxy_express', 'proxy_fcgi', 'proxy_fdpass', 'proxy_ftp', 'proxy_html', 'proxy_http', 'proxy_scgi', 'proxy_wstunnel', 'ratelimit', 'reflector', 'remoteip', 'reqtimeout', 'request', 'rewrite', 'sed', 'session', 'session_cookie', 'session_crypto', 'session_dbd', 'setenvif', 'slotmem_plain', 'slotmem_shm', 'socache_dbm', 'socache_memcache', 'socache_shmcb', 'speling', 'ssl', 'status', 'substitute', 'suexec', 'unique_id', 'userdir', 'usertrack', 'vhost_alias', 'xml2enc']; +$availableExtensions = ['access_compat', 'actions', 'alias', 'allowmethods', 'asis', 'auth_basic', 'auth_digest', 'auth_form', 'authn_anon', 'authn_core', 'authn_dbd', 'authn_dbm', 'authn_file', 'authn_socache', 'authnz_fcgi', 'authnz_ldap', 'authz_core', 'authz_dbd', 'authz_dbm', 'authz_groupfile', 'authz_host', 'authz_owner', 'authz_user', 'autoindex', 'brotli', 'buffer', 'cache', 'cache_disk', 'cache_socache', 'cern_meta', 'cgi', 'cgid', 'charset_lite', 'data', 'dav', 'dav_fs', 'dav_lock', 'dbd', 'deflate', 'dialup', 'dir', 'dump_io', 'echo', 'env', 'expires', 'ext_filter', 'file_cache', 'filter', 'headers', 'heartbeat', 'heartmonitor', 'ident', 'imagemap', 'include', 'info', 'lbmethod_bybusyness', 'lbmethod_byrequests', 'lbmethod_bytraffic', 'lbmethod_heartbeat', 'ldap', 'log_debug', 'log_forensic', 'lua', 'macro', 'md', 'mime', 'mime_magic', 'mpm_event', 'mpm_prefork', 'mpm_worker', 'negotiation', 'php'.getenv('PHP_VERSION'), 'proxy', 'proxy_ajp', 'proxy_balancer', 'proxy_connect', 'proxy_express', 'proxy_fcgi', 'proxy_fdpass', 'proxy_ftp', 'proxy_hcheck', 'proxy_html', 'proxy_http', 'proxy_scgi', 'proxy_uwsgi', 'proxy_wstunnel', 'ratelimit', 'reflector', 'remoteip', 'reqtimeout', 'request', 'rewrite', 'sed', 'session', 'session_cookie', 'session_crypto', 'session_dbd', 'setenvif', 'slotmem_plain', 'slotmem_shm', 'socache_dbm', 'socache_memcache', 'socache_redis', 'socache_shmcb', 'speling', 'ssl', 'status', 'substitute', 'suexec', 'unique_id', 'userdir', 'usertrack', 'vhost_alias', 'xml2enc']; $delimiter = [',', '|', ';', ':']; $replace = str_replace($delimiter, ' ', getenv('APACHE_EXTENSIONS')); diff --git a/utils/install_selected_extensions.sh b/utils/install_selected_extensions.sh index 28f8d93..f96edf4 100755 --- a/utils/install_selected_extensions.sh +++ b/utils/install_selected_extensions.sh @@ -1,6 +1,12 @@ #!/bin/bash set -e + +if [ -z "${PHP_EXTENSIONS// /}" ]; then + echo "No extensions installed in ONBUILD hook." + exit 0 +fi + set -x # Let's disable autoclean of package list after apt-get install @@ -16,4 +22,5 @@ mv /tmp/docker-clean /etc/apt/apt.conf.d/docker-clean apt-get purge -y php-pear build-essential php${PHP_VERSION}-dev pkg-config apt-get autoremove -y apt-get clean -rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* +rm -rf /var/lib/apt/lists/* /var/cache/apt/*.bin /tmp/* /var/tmp/* /usr/share/doc/* +rm -f /usr/local/bin/pickle