diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5a083..35bda06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ ### Minor changes * **2026-10-09** + * Upgrade Supercronic from 0.1.9 to 0.2.49 (built with an up-to-date Go version) + * Fix Composer and NodeJS binaries (`vendor/bin`, `node_modules/.bin` and global Composer binaries) not found by `docker exec` / `docker compose exec` when run without their path + * Fix extensions that could not be loaded without another extension: the required extensions are now enabled with them (`memcached` requires `igbinary` and `msgpack`, `redis` requires `igbinary`, `mailparse` requires `mbstring` and `swoole` requires `curl`) + * Fix the container start when the mounted directory belongs to an ID used by a system account of the image (e.g. 998 for a `gitlab-runner` user): the commands were run with this account (no home directory, no sudo) instead of the `docker` user + * Fix `DOCKER_USER` set to an ID used by a system account of the image (`usermod: UID already exists`) + * Fix `php` run by a user that cannot use sudo (e.g. `sudo -u www-data php ...`): sudo password errors, and failure when a `PHP_*` variable had changed + * Fix PHP warnings (e.g. an extension that cannot be loaded) breaking the container start: they were written in `generated_conf.ini`, in the crontab and in the startup commands * 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` diff --git a/Dockerfile.slim.apache b/Dockerfile.slim.apache index 8706191..24b60e1 100644 --- a/Dockerfile.slim.apache +++ b/Dockerfile.slim.apache @@ -252,7 +252,9 @@ WORKDIR /var/www/html # | # | Let's add ./vendor/bin to the PATH (utility function to use Composer bin easily) # | -ENV PATH="$PATH:./vendor/bin:~/.composer/vendor/bin" + +ENV PATH="$PATH:/var/www/html/vendor/bin:/home/docker/.composer/vendor/bin:./vendor/bin:~/.composer/vendor/bin" + RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./vendor/bin:~/.composer/vendor/bin#g' /etc/sudoers USER docker @@ -291,7 +293,9 @@ USER root # | # | NodeJS path registration (if we install NodeJS, this is useful). # | -ENV PATH="$PATH:./node_modules/.bin" + +ENV PATH="$PATH:/var/www/html/node_modules/.bin:./node_modules/.bin" + RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./node_modules/.bin#g' /etc/sudoers # |-------------------------------------------------------------------------- @@ -372,10 +376,10 @@ ENV SUPERCRONIC_OPTIONS="" ONBUILD ARG INSTALL_CRON ONBUILD RUN if [ -n "$INSTALL_CRON" ]; then \ SUPERCRONIC="supercronic-${TARGETOS}-${TARGETARCH}" \ - && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.1.9/${SUPERCRONIC}" \ + && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.2.49/${SUPERCRONIC}" \ && echo ${SUPERCRONIC_URL} \ - && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=e2714c43e7781bf1579c85aa61259245f56dbba1; \ - elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=5ddf8ea26b56d4a7ff6faecdd8966610d5cb9d85; \ + && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=0b6c5bb743e0b0dafed1132198c81807927ac413; \ + elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=e63c11a9726b775a6a11801e81af4f3fb926aa68; \ else echo "Target arch '${TARGETARCH}' is not supported"; exit 1; fi \ && curl -fsSLO --retry 5 --retry-delay 2 "${SUPERCRONIC_URL}" \ && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ diff --git a/Dockerfile.slim.cli b/Dockerfile.slim.cli index dc40e27..5a242b8 100644 --- a/Dockerfile.slim.cli +++ b/Dockerfile.slim.cli @@ -178,7 +178,9 @@ WORKDIR /usr/src/app # | # | Let's add ./vendor/bin to the PATH (utility function to use Composer bin easily) # | -ENV PATH="$PATH:./vendor/bin:~/.composer/vendor/bin" + +ENV PATH="$PATH:/usr/src/app/vendor/bin:/home/docker/.composer/vendor/bin:./vendor/bin:~/.composer/vendor/bin" + RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./vendor/bin:~/.composer/vendor/bin#g' /etc/sudoers USER docker @@ -217,7 +219,9 @@ USER root # | # | NodeJS path registration (if we install NodeJS, this is useful). # | -ENV PATH="$PATH:./node_modules/.bin" + +ENV PATH="$PATH:/usr/src/app/node_modules/.bin:./node_modules/.bin" + RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./node_modules/.bin#g' /etc/sudoers # |-------------------------------------------------------------------------- @@ -277,10 +281,10 @@ ENV SUPERCRONIC_OPTIONS="" ONBUILD ARG INSTALL_CRON ONBUILD RUN if [ -n "$INSTALL_CRON" ]; then \ SUPERCRONIC="supercronic-${TARGETOS}-${TARGETARCH}" \ - && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.1.9/${SUPERCRONIC}" \ + && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.2.49/${SUPERCRONIC}" \ && echo ${SUPERCRONIC_URL} \ - && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=e2714c43e7781bf1579c85aa61259245f56dbba1; \ - elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=5ddf8ea26b56d4a7ff6faecdd8966610d5cb9d85; \ + && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=0b6c5bb743e0b0dafed1132198c81807927ac413; \ + elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=e63c11a9726b775a6a11801e81af4f3fb926aa68; \ else echo "Target arch '${TARGETARCH}' is not supported"; exit 1; fi \ && curl -fsSLO --retry 5 --retry-delay 2 "${SUPERCRONIC_URL}" \ && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ diff --git a/Dockerfile.slim.fpm b/Dockerfile.slim.fpm index 7b454d7..3633cc2 100644 --- a/Dockerfile.slim.fpm +++ b/Dockerfile.slim.fpm @@ -195,7 +195,9 @@ WORKDIR /var/www/html # | # | Let's add ./vendor/bin to the PATH (utility function to use Composer bin easily) # | -ENV PATH="$PATH:./vendor/bin:~/.composer/vendor/bin" + +ENV PATH="$PATH:/var/www/html/vendor/bin:/home/docker/.composer/vendor/bin:./vendor/bin:~/.composer/vendor/bin" + RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./vendor/bin:~/.composer/vendor/bin#g' /etc/sudoers USER docker @@ -234,7 +236,9 @@ USER root # | # | NodeJS path registration (if we install NodeJS, this is useful). # | -ENV PATH="$PATH:./node_modules/.bin" + +ENV PATH="$PATH:/var/www/html/node_modules/.bin:./node_modules/.bin" + RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./node_modules/.bin#g' /etc/sudoers # |-------------------------------------------------------------------------- @@ -300,10 +304,10 @@ ENV SUPERCRONIC_OPTIONS="" ONBUILD ARG INSTALL_CRON ONBUILD RUN if [ -n "$INSTALL_CRON" ]; then \ SUPERCRONIC="supercronic-${TARGETOS}-${TARGETARCH}" \ - && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.1.9/${SUPERCRONIC}" \ + && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.2.49/${SUPERCRONIC}" \ && echo ${SUPERCRONIC_URL} \ - && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=e2714c43e7781bf1579c85aa61259245f56dbba1; \ - elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=5ddf8ea26b56d4a7ff6faecdd8966610d5cb9d85; \ + && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=0b6c5bb743e0b0dafed1132198c81807927ac413; \ + elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=e63c11a9726b775a6a11801e81af4f3fb926aa68; \ else echo "Target arch '${TARGETARCH}' is not supported"; exit 1; fi \ && curl -fsSLO --retry 5 --retry-delay 2 "${SUPERCRONIC_URL}" \ && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ diff --git a/README.md b/README.md index 684615e..8328d55 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This repository contains a set of developer-friendly, general purpose PHP images - Fat images are bundled with [Supercronic](https://github.com/aptible/supercronic) which is a Cron compatible task runner. Cron jobs can be configured using environment variables - Fat images come with [Composer](https://getcomposer.org/) and [Prestissimo](https://github.com/hirak/prestissimo) installed - All variants can be installed with or without NodeJS (if you need to build your static assets). + - Composer binaries (`vendor/bin` and global binaries) and NodeJS binaries (`node_modules/.bin`) can be run without their path, including with `docker exec` / `docker compose exec` (e.g. `docker compose exec app phpstan`) - Everything is done to limit file permission issues that often arise when using Docker. The image is actively tested on Linux, Windows and MacOS @@ -195,6 +196,7 @@ This list can be outdated, you can verify by executing : `docker run --rm -it th - *ev* is not available in PHP 8.1+ - *mcrypt* is deprecated and its usage is discouraged: it is provided for legacy applications only +- The extensions required by an enabled extension are enabled with it, even if they are disabled: `igbinary` and `msgpack` for *memcached*, `igbinary` for *redis*, `mbstring` for *mailparse*, `curl` and `mysqlnd` for *swoole*, `mysqlnd` for *mysqli* and *pdo_mysql* ### Enabling/disabling extensions in the fat image @@ -423,6 +425,10 @@ working directory (`/var/www/html` for Apache/PHP-FPM, or `/usr/src/app` for CLI you want to run commands as this user. So it will **dynamically change the ID of the docker user** to match the ID of the current working directory user. +If this ID is already used by a system account of the image (e.g. `998`, used by `systemd-network` in the image and +often by the `gitlab-runner` user on CI hosts), this system account is moved to another ID: the commands are still run +by the `docker` user. + Furthermore, the image is changing the Apache default user/group to be `docker/docker` (instead if `www-data/www-data`). So Apache will run with the same rights as the user on your host. diff --git a/tests-suite/assets/php-startup-warning.ini b/tests-suite/assets/php-startup-warning.ini new file mode 100644 index 0000000..d242910 --- /dev/null +++ b/tests-suite/assets/php-startup-warning.ini @@ -0,0 +1,2 @@ +; Loading an extension that does not exist makes PHP display a startup warning +extension=does_not_exist diff --git a/tests-suite/php-extensions.sh b/tests-suite/php-extensions.sh index c52d88c..a6f56b1 100755 --- a/tests-suite/php-extensions.sh +++ b/tests-suite/php-extensions.sh @@ -46,6 +46,19 @@ test_presenceOfPhp82ExtensionsOnFat() { assert_equals "${EXTENSIONS[$EXTENSION]}" "${RESULT}" "Missing php-${EXTENSION}" done } +################################################################# +## Let's check that the extensions required by an enabled +## extension are enabled too (even if explicitly disabled) +################################################################# +test_dependenciesOfExtensionsOnFat() { + MODULES=$(docker run ${RUN_OPTIONS} -e "PHP_EXTENSIONS=memcached mailparse" -e PHP_EXTENSION_IGBINARY=0 -e PHP_EXTENSION_MBSTRING=0 \ + --rm "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-${BRANCH_VARIANT}${ARCH_SUFFIX}" php -m 2>&1 | tail -n +1) + assert_equals "0" "$(echo "${MODULES}" | grep -c 'Unable to load dynamic library')" "An extension cannot be loaded" + for EXTENSION in memcached igbinary msgpack mailparse mbstring redis; do + RESULT=$(echo "${MODULES}" | grep --color=never -x "${EXTENSION}") + assert_equals "${EXTENSION}" "${RESULT}" "Missing php-${EXTENSION}" + done +} ############################################################ ## Let's check that the extensions are enabled when composer is run ############################################################ diff --git a/tests-suite/tool-composer-bin.sh b/tests-suite/tool-composer-bin.sh new file mode 100755 index 0000000..49fd8fa --- /dev/null +++ b/tests-suite/tool-composer-bin.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +. ./config + +############################################################ +## Composer binaries can be run by "docker exec" without their path +############################################################ +test_composerBinaryWithDockerExec() { + mkdir -p "${TMP_DIR}/vendor/bin" + printf '#!/bin/sh\necho composer-bin-ok\n' > "${TMP_DIR}/vendor/bin/composer-bin-test" + chmod -R a+rX "${TMP_DIR}" && chmod a+x "${TMP_DIR}/vendor/bin/composer-bin-test" + docker run --name "${COMPOSER_BIN_CONTAINER_NAME}" ${RUN_OPTIONS} --rm -d -v "${TMP_DIR}":"${CONTAINER_CWD}" \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" sleep 30 > /dev/null + assert_equals "0" "$?" "Docker run failed" + RESULT="$(docker exec "${COMPOSER_BIN_CONTAINER_NAME}" composer-bin-test 2>&1)" + assert_equals "composer-bin-ok" "${RESULT}" +} + +setup_suite() { + export TMP_DIR="$(mktemp -d)" + export COMPOSER_BIN_CONTAINER_NAME="test-composer-bin-$(unused_port)" + if [[ $VARIANT == cli* ]]; then export CONTAINER_CWD=/usr/src/app; else export CONTAINER_CWD=/var/www/html; fi +} + +teardown_suite() { + docker rm -f "${COMPOSER_BIN_CONTAINER_NAME}" > /dev/null 2>&1 + if [[ "" != ${TMP_DIR} ]]; then docker run ${RUN_OPTIONS} --rm -v "/tmp":/tmp busybox rm -rf "${TMP_DIR}" > /dev/null 2>&1; fi +} diff --git a/tests-suite/tool-startup-command.sh b/tests-suite/tool-startup-command.sh index bce45d7..ec6f615 100755 --- a/tests-suite/tool-startup-command.sh +++ b/tests-suite/tool-startup-command.sh @@ -25,3 +25,13 @@ test_withFile() { "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" php -m 2>/dev/null | grep -q "startup.sh executed" assert_equals "0" "$?" } +############################################################ +## Tests that a PHP startup warning does not prevent the container from starting +############################################################ +test_phpStartupWarning() { + RESULT="$(docker run ${RUN_OPTIONS} --rm -e STARTUP_COMMAND_1="echo startup-ok" \ + -v "${SCRIPT_DIR}/assets/php-startup-warning.ini":"/etc/php/${PHP_VERSION}/cli/conf.d/99-startup-warning.ini" \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" sleep 1 2>/dev/null)" + assert_equals "0" "$?" "Docker run failed" + assert_equals "startup-ok" "$RESULT" +} diff --git a/tests-suite/users-rights.sh b/tests-suite/users-rights.sh index abdfc61..aa80845 100755 --- a/tests-suite/users-rights.sh +++ b/tests-suite/users-rights.sh @@ -36,7 +36,7 @@ test_defaultUserCanWriteOnStdoutAndStderr() { ############################################################ ## It's also works for users with existing IDs in the container ############################################################ -test_defaultUserCanWriteOnStdoutAndStderr() { +test_defaultUserCanBeAnExistingUser() { mkdir -p "${TMP_DIR}/user33" cat << EOF > "${TMP_DIR}/user33/composer.json" { @@ -58,6 +58,32 @@ EOF } +############################################################ +## The system accounts of the image (systemd-network, polkitd...) +## do not take the place of the default user when they have the +## ID of the mounted directory (e.g. 998 for gitlab-runner) +############################################################ +test_defaultUserTakesUidOfSystemAccount() { + mkdir -p "${TMP_DIR}/user998" + docker run ${RUN_OPTIONS} --rm -v /tmp:/tmp busybox chown 998:998 "${TMP_DIR}/user998" > /dev/null 2>&1 + RESULT="$(docker run ${RUN_OPTIONS} --rm -v "${TMP_DIR}/user998":"${CONTAINER_CWD}" -e STARTUP_COMMAND_1='touch ~/.startup_done' \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" \ + bash -c 'echo "$(id -un):$(id -ur)"')" + assert_equals "0" "$?" "Docker run failed" + assert_equals "docker:998" "${RESULT}" "Default user mismatch with a mounted directory owned by a system account ID" +} + +############################################################ +## Users that cannot use sudo can run PHP, even when a PHP_* +## environment variable has changed +############################################################ +test_userWithoutSudoCanRunPhp() { + RESULT="$(docker run ${RUN_OPTIONS} --rm "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" \ + sudo -E -u www-data PHP_INI_MEMORY_LIMIT=1G php -r 'echo "OK";' 2>&1)" + assert_equals "0" "$?" "Docker run failed" + assert_equals "OK" "${RESULT}" "PHP run as www-data failed or printed errors" +} + setup_suite() { export TMP_DIR="$(mktemp -d)" if [[ $VARIANT == cli* ]]; then export CONTAINER_CWD=/usr/src/app; else export CONTAINER_CWD=/var/www/html; fi diff --git a/utils/Dockerfile.slim.blueprint b/utils/Dockerfile.slim.blueprint index 0a70de9..198ddd9 100644 --- a/utils/Dockerfile.slim.blueprint +++ b/utils/Dockerfile.slim.blueprint @@ -271,7 +271,11 @@ WORKDIR /var/www/html # | # | Let's add ./vendor/bin to the PATH (utility function to use Composer bin easily) # | -ENV PATH="$PATH:./vendor/bin:~/.composer/vendor/bin" +{{if eq .Orbit.variant "cli" }} +ENV PATH="$PATH:/usr/src/app/vendor/bin:/home/docker/.composer/vendor/bin:./vendor/bin:~/.composer/vendor/bin" +{{else}} +ENV PATH="$PATH:/var/www/html/vendor/bin:/home/docker/.composer/vendor/bin:./vendor/bin:~/.composer/vendor/bin" +{{end}} RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./vendor/bin:~/.composer/vendor/bin#g' /etc/sudoers USER docker @@ -310,7 +314,11 @@ USER root # | # | NodeJS path registration (if we install NodeJS, this is useful). # | -ENV PATH="$PATH:./node_modules/.bin" +{{if eq .Orbit.variant "cli" }} +ENV PATH="$PATH:/usr/src/app/node_modules/.bin:./node_modules/.bin" +{{else}} +ENV PATH="$PATH:/var/www/html/node_modules/.bin:./node_modules/.bin" +{{end}} RUN sed -i 's#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:./node_modules/.bin#g' /etc/sudoers # |-------------------------------------------------------------------------- @@ -401,10 +409,10 @@ ENV SUPERCRONIC_OPTIONS="" ONBUILD ARG INSTALL_CRON ONBUILD RUN if [ -n "$INSTALL_CRON" ]; then \ SUPERCRONIC="supercronic-${TARGETOS}-${TARGETARCH}" \ - && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.1.9/${SUPERCRONIC}" \ + && SUPERCRONIC_URL="https://github.com/aptible/supercronic/releases/download/v0.2.49/${SUPERCRONIC}" \ && echo ${SUPERCRONIC_URL} \ - && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=e2714c43e7781bf1579c85aa61259245f56dbba1; \ - elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=5ddf8ea26b56d4a7ff6faecdd8966610d5cb9d85; \ + && if [ "$TARGETARCH" = "arm64" ]; then SUPERCRONIC_SHA1SUM=0b6c5bb743e0b0dafed1132198c81807927ac413; \ + elif [ "$TARGETARCH" = "amd64" ]; then SUPERCRONIC_SHA1SUM=e63c11a9726b775a6a11801e81af4f3fb926aa68; \ else echo "Target arch '${TARGETARCH}' is not supported"; exit 1; fi \ && curl -fsSLO --retry 5 --retry-delay 2 "${SUPERCRONIC_URL}" \ && echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - \ diff --git a/utils/README.blueprint.md b/utils/README.blueprint.md index bf7d61d..ac6f54c 100644 --- a/utils/README.blueprint.md +++ b/utils/README.blueprint.md @@ -11,6 +11,7 @@ This repository contains a set of developer-friendly, general purpose PHP images - Fat images are bundled with [Supercronic](https://github.com/aptible/supercronic) which is a Cron compatible task runner. Cron jobs can be configured using environment variables - Fat images come with [Composer](https://getcomposer.org/) and [Prestissimo](https://github.com/hirak/prestissimo) installed - All variants can be installed with or without NodeJS (if you need to build your static assets). + - Composer binaries (`vendor/bin` and global binaries) and NodeJS binaries (`node_modules/.bin`) can be run without their path, including with `docker exec` / `docker compose exec` (e.g. `docker compose exec app phpstan`) - Everything is done to limit file permission issues that often arise when using Docker. The image is actively tested on Linux, Windows and MacOS {{ $image := .Orbit.Images }} @@ -125,6 +126,7 @@ This list can be outdated, you can verify by executing : `docker run --rm -it th - *ev* is not available in PHP 8.1+ - *mcrypt* is deprecated and its usage is discouraged: it is provided for legacy applications only +- The extensions required by an enabled extension are enabled with it, even if they are disabled: `igbinary` and `msgpack` for *memcached*, `igbinary` for *redis*, `mbstring` for *mailparse*, `curl` and `mysqlnd` for *swoole*, `mysqlnd` for *mysqli* and *pdo_mysql* ### Enabling/disabling extensions in the fat image @@ -353,6 +355,10 @@ working directory (`/var/www/html` for Apache/PHP-FPM, or `/usr/src/app` for CLI you want to run commands as this user. So it will **dynamically change the ID of the docker user** to match the ID of the current working directory user. +If this ID is already used by a system account of the image (e.g. `998`, used by `systemd-network` in the image and +often by the `gitlab-runner` user on CI hosts), this system account is moved to another ID: the commands are still run +by the `docker` user. + Furthermore, the image is changing the Apache default user/group to be `docker/docker` (instead if `www-data/www-data`). So Apache will run with the same rights as the user on your host. diff --git a/utils/docker-entrypoint-as-root.sh b/utils/docker-entrypoint-as-root.sh index d2e3b13..539ea9f 100755 --- a/utils/docker-entrypoint-as-root.sh +++ b/utils/docker-entrypoint-as-root.sh @@ -66,6 +66,24 @@ fi # DOCKER_USER is a user name if the user exists in the container, otherwise, it is a user ID (from a user on the host). +# A system account of the image that has the ID of DOCKER_USER is moved to a free ID: the docker user takes this ID +SYS_UID_MIN=$(awk '$1 == "SYS_UID_MIN" { print $2 }' /etc/login.defs) +SYS_UID_MAX=$(awk '$1 == "SYS_UID_MAX" { print $2 }' /etc/login.defs) +SYS_UID_MIN=${SYS_UID_MIN:-100} +SYS_UID_MAX=${SYS_UID_MAX:-999} +SYSTEM_ACCOUNT=$(getent passwd "$DOCKER_USER" | cut -d: -f1,3) +SYSTEM_ACCOUNT_NAME=${SYSTEM_ACCOUNT%%:*} +SYSTEM_ACCOUNT_ID=${SYSTEM_ACCOUNT##*:} +if [[ -n "$SYSTEM_ACCOUNT" ]] && [[ "$SYSTEM_ACCOUNT_NAME" != "docker" ]] && (( SYSTEM_ACCOUNT_ID >= SYS_UID_MIN && SYSTEM_ACCOUNT_ID <= SYS_UID_MAX )); then + FREE_ID=$SYS_UID_MAX + while getent passwd "$FREE_ID" > /dev/null; do + FREE_ID=$((FREE_ID - 1)) + done + sed -i "s/^${SYSTEM_ACCOUNT_NAME}:\([^:]*\):${SYSTEM_ACCOUNT_ID}:/${SYSTEM_ACCOUNT_NAME}:\1:${FREE_ID}:/" /etc/passwd + DOCKER_USER=$SYSTEM_ACCOUNT_ID +fi +unset SYS_UID_MIN SYS_UID_MAX SYSTEM_ACCOUNT SYSTEM_ACCOUNT_NAME SYSTEM_ACCOUNT_ID FREE_ID + # If DOCKER_USER is an ID, let's if [[ "$DOCKER_USER" =~ ^[0-9]+$ ]] ; then # MAIN_DIR_USER is a user ID. @@ -123,14 +141,14 @@ unset DOCKER_FOR_MAC_REMOTE_HOST unset REMOTE_HOST_FOUND sudo chown docker:docker /opt/php_env_var_cache.php -/usr/bin/real_php /usr/local/bin/check_php_env_var_changes.php &> /dev/null +/usr/bin/real_php -d display_errors=stderr /usr/local/bin/check_php_env_var_changes.php &> /dev/null -/usr/bin/real_php /usr/local/bin/generate_conf.php > /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini -PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php /usr/local/bin/setup_extensions.php | sudo bash +/usr/bin/real_php -d display_errors=stderr /usr/local/bin/generate_conf.php > /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini +PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php -d display_errors=stderr /usr/local/bin/setup_extensions.php | sudo bash # output on the logs can be done by writing on the "tini" PID. Useful for CRONTAB TINI_PID=`ps -e | grep tini | awk '{print $1;}'` -/usr/bin/real_php /usr/local/bin/generate_cron.php $TINI_PID > /tmp/generated_crontab +/usr/bin/real_php -d display_errors=stderr /usr/local/bin/generate_cron.php $TINI_PID > /tmp/generated_crontab chmod 0644 /tmp/generated_crontab # If generated_crontab is not empty, start supercronic @@ -139,13 +157,13 @@ if [[ -s /tmp/generated_crontab ]]; then fi if [[ "$IMAGE_VARIANT" == "apache" ]]; then - /usr/bin/real_php /usr/local/bin/enable_apache_mods.php | bash + /usr/bin/real_php -d display_errors=stderr /usr/local/bin/enable_apache_mods.php | bash fi if [ -e /etc/container/startup.sh ]; then sudo -E -u "#$DOCKER_USER_ID" /etc/container/startup.sh fi -sudo -E -u "#$DOCKER_USER_ID" sh -c "/usr/bin/real_php /usr/local/bin/startup_commands.php | bash" +sudo -E -u "#$DOCKER_USER_ID" sh -c "/usr/bin/real_php -d display_errors=stderr /usr/local/bin/startup_commands.php | bash" if [[ "$APACHE_DOCUMENT_ROOT" == /* ]]; then export ABSOLUTE_APACHE_DOCUMENT_ROOT="$APACHE_DOCUMENT_ROOT" diff --git a/utils/php_proxy.sh b/utils/php_proxy.sh index d3dc5a0..f1ba90c 100755 --- a/utils/php_proxy.sh +++ b/utils/php_proxy.sh @@ -1,8 +1,11 @@ #!/bin/bash -sudo chown docker:docker /opt/php_env_var_cache.php +# Users that cannot use sudo run PHP with the current configuration +if ! sudo -n chown docker:docker /opt/php_env_var_cache.php 2> /dev/null; then + exec /usr/bin/real_php "$@" +fi -REGENERATE=$(/usr/bin/real_php /usr/local/bin/check_php_env_var_changes.php) +REGENERATE=$(/usr/bin/real_php -d display_errors=stderr /usr/local/bin/check_php_env_var_changes.php) if [[ "$REGENERATE" != "0" ]] && [[ "$REGENERATE" != "1" ]]; then >&2 echo "Unexpected PHP proxy output:" @@ -11,8 +14,8 @@ if [[ "$REGENERATE" != "0" ]] && [[ "$REGENERATE" != "1" ]]; then fi if [[ "$REGENERATE" == "1" ]]; then - /usr/bin/real_php /usr/local/bin/generate_conf.php | sudo tee "/etc/php/${PHP_VERSION}/mods-available/generated_conf.ini" > /dev/null - PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php /usr/local/bin/setup_extensions.php | sudo bash + /usr/bin/real_php -d display_errors=stderr /usr/local/bin/generate_conf.php | sudo tee "/etc/php/${PHP_VERSION}/mods-available/generated_conf.ini" > /dev/null + PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php -d display_errors=stderr /usr/local/bin/setup_extensions.php | sudo bash fi exec /usr/bin/real_php "$@" diff --git a/utils/setup_extensions.php b/utils/setup_extensions.php index dc3ebdd..19a31c3 100755 --- a/utils/setup_extensions.php +++ b/utils/setup_extensions.php @@ -55,10 +55,25 @@ } } -// mysqlnd is a dependency required for mysqli, pdo_mysql or swoole -if (enableExtension('mysqli') || enableExtension('pdo_mysql') || enableExtension('swoole')) { - $toEnable['mysqlnd'] = 'mysqlnd'; - unset($toDisable['mysqlnd']); +// Extensions that cannot be loaded without other extensions (dependencies of the Ubuntu packages) +$dependencies = [ + 'mailparse' => ['mbstring'], + 'memcached' => ['igbinary', 'msgpack'], + 'mysqli' => ['mysqlnd'], + 'pdo_mysql' => ['mysqlnd'], + 'redis' => ['igbinary'], + 'swoole' => ['curl', 'mysqlnd'], +]; +foreach ($dependencies as $extension => $requiredExtensions) { + if (!isset($toEnable[$extension])) { + continue; + } + foreach ($requiredExtensions as $requiredExtension) { + if (in_array($requiredExtension, $availableExtensions, true)) { + $toEnable[$requiredExtension] = $requiredExtension; + unset($toDisable[$requiredExtension]); + } + } } // curl is a dependency required for blackfire 8 (see https://blog.blackfire.io/php-8-support.html)