Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
6 changes: 5 additions & 1 deletion Dockerfile.slim.fpm
Original file line number Diff line number Diff line change
Expand Up @@ -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




Expand Down Expand Up @@ -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


Expand Down
2 changes: 1 addition & 1 deletion extensions/core/install_all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 26 additions & 0 deletions tests-suite/variant-apache.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -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
}
35 changes: 35 additions & 0 deletions tests-suite/variant-fpm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 5 additions & 1 deletion utils/Dockerfile.slim.blueprint
Original file line number Diff line number Diff line change
Expand Up @@ -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}}


Expand Down Expand Up @@ -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}}

Expand Down
3 changes: 3 additions & 0 deletions utils/apache2-foreground
Original file line number Diff line number Diff line change
@@ -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 <apache args>", it does not use "exec", which leaves an undesirable resident shell process)

Expand Down
6 changes: 6 additions & 0 deletions utils/docker-entrypoint-as-root.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion utils/enable_apache_mods.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down
9 changes: 8 additions & 1 deletion utils/install_selected_extensions.sh
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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