From fde4c4aa74e4dc8a6f36c4e95f36c84a2bdf9d52 Mon Sep 17 00:00:00 2001 From: Mistral OZ - MIO Date: Thu, 8 Oct 2026 06:50:43 +0200 Subject: [PATCH 1/3] Remove apt binary caches left in images built from slim and in fat images The extension install scripts disable the docker-clean apt configuration while installing: apt-get update then creates /var/cache/apt/pkgcache.bin and srcpkgcache.bin (~105MB) that apt-get clean no longer knows once docker-clean is restored, so they stayed in the image layer. - The ONBUILD hook no longer runs apt when PHP_EXTENSIONS is empty - The apt binary caches are removed explicitly (ONBUILD hook and fat images) --- CHANGELOG.md | 4 ++++ extensions/core/install_all.sh | 2 +- utils/install_selected_extensions.sh | 9 ++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7837ac5..fc8efe2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ### Minor changes +* **2026-10-09** + * 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/extensions/core/install_all.sh b/extensions/core/install_all.sh index 19cfa543..29bd096d 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/utils/install_selected_extensions.sh b/utils/install_selected_extensions.sh index 28f8d93c..f96edf4b 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 From ee084b59f4ff2f2cc89d73a075c9d36584610d63 Mon Sep 17 00:00:00 2001 From: Mistral OZ - MIO Date: Thu, 8 Oct 2026 06:51:59 +0200 Subject: [PATCH 2/3] Fix graceful stop of the fpm variant and stop requests sent during startup - fpm variant: STOPSIGNAL SIGQUIT (PHP-FPM graceful stop, running requests are completed) and EXPOSE 9000 - Stop signals ignored by bash were lost while the container was initializing, and the container was killed after the stop timeout: SIGQUIT (fpm) is always ignored by bash, SIGWINCH (apache) by default. The entrypoint and apache2-foreground now trap them. SIGTERM and SIGINT (cli) are not trapped: they already stop bash right away. - Fix the sed commenting the default listen socket in www.conf (single quotes prevented the PHP_VERSION expansion) --- CHANGELOG.md | 2 ++ Dockerfile.slim.fpm | 6 ++++- tests-suite/variant-apache.sh | 16 ++++++++++++++ tests-suite/variant-fpm.sh | 35 ++++++++++++++++++++++++++++++ utils/Dockerfile.slim.blueprint | 6 ++++- utils/apache2-foreground | 3 +++ utils/docker-entrypoint-as-root.sh | 6 +++++ 7 files changed, 72 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc8efe2c..f9eee951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### 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 * 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) diff --git a/Dockerfile.slim.fpm b/Dockerfile.slim.fpm index 99f92993..7b454d75 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/tests-suite/variant-apache.sh b/tests-suite/variant-apache.sh index 3555b14a..bf214ef8 100755 --- a/tests-suite/variant-apache.sh +++ b/tests-suite/variant-apache.sh @@ -41,7 +41,22 @@ test_changeMemoryLimit() { assert_equals "2G" "$RESULT" "Apache PHP_INI_MEMORY_LIMIT was not applied" } +############################################################ +## 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 +83,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 6fc5e119..8782ca37 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 4cf8fa0a..0a70de97 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 5fe22e26..5cca3368 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 70f2eb80..d2e3b13d 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 From 1d1837239905557664e71a78d33bbd3ceaca9686 Mon Sep 17 00:00:00 2001 From: Mistral OZ - MIO Date: Thu, 8 Oct 2026 07:11:38 +0200 Subject: [PATCH 3/3] Accept all the Apache modules shipped by Ubuntu in APACHE_EXTENSION_* brotli, cern_meta, imagemap, md, proxy_hcheck, proxy_uwsgi and socache_redis were listed as available in the README but silently ignored (APACHE_EXTENSION_*) or rejected (APACHE_EXTENSIONS). --- CHANGELOG.md | 1 + tests-suite/variant-apache.sh | 10 ++++++++++ utils/enable_apache_mods.php | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9eee951..3b5a083d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * **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) diff --git a/tests-suite/variant-apache.sh b/tests-suite/variant-apache.sh index bf214ef8..3cd61758 100755 --- a/tests-suite/variant-apache.sh +++ b/tests-suite/variant-apache.sh @@ -41,6 +41,16 @@ 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 ############################################################ diff --git a/utils/enable_apache_mods.php b/utils/enable_apache_mods.php index 513b00a8..f921b3a3 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'));