From c7befe07b49abf2c198cd3e59c9bf40c67d74c7d Mon Sep 17 00:00:00 2001 From: Lorenzo Rogai Date: Wed, 22 Jul 2026 15:54:04 +0200 Subject: [PATCH 1/2] fix(s6): make web services wait for their config oneshots to fix root-mode startup race When a container built on the s6 images runs as root, php-fpm and the web server (nginx/apache2) are brought up in parallel with the entrypoint oneshots that configure them, because the long-running services have no dependency on those oneshots. As root this races: - php-fpm reads its pool before `5-fpm-pool-user` appends `user`/`group`, failing with "ALERT: [pool www] user has not been defined" -> "ERROR: FPM initialization failed". - the web server starts before `10-init-webserver-config` renders its config (e.g. nginx: open() "/etc/nginx/nginx.conf" failed). s6 restarts the crashed services so the container eventually recovers, which is why the failure is intermittent and hard to reproduce (see discussion #425), but it emits alarming errors, slows startup, and leaves a brief window with no service. docker-php-serversideup-s6-init now adds a dependency from each web service to the entrypoint oneshot that configures it, appending to the existing flat `dependencies` file. The oneshots are chained in alphabetical order, so depending on one transitively waits for all earlier ones (php-fpm -> 5-fpm-pool-user; nginx/apache2 -> 10-init-webserver-config). Entries are de-duplicated and appended newline-safely (nginx's shipped `dependencies` has no trailing newline). Dependencies are only added when both the service and the oneshot exist, so cli/fpm/frankenphp images and images that remove a script are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../local/bin/docker-php-serversideup-s6-init | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-init b/src/s6/usr/local/bin/docker-php-serversideup-s6-init index d61307140..6afdf5b6e 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-init +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-init @@ -77,4 +77,37 @@ for file in "$ENTRYPOINT_DIR"/*.sh; do echo "Skipping ${script_name} because it already exists at ${S6_HOME}/scripts/${script_name}" fi -done \ No newline at end of file +done + +# Make the long-running services wait for the entrypoint oneshots that configure +# them. When the container runs as root, php-fpm and the web server otherwise +# start in parallel with these oneshots and can lose the race: php-fpm reads the +# pool before "5-fpm-pool-user" adds "user = www-data" (ALERT: [pool www] user +# has not been defined -> FPM initialization failed), and the web server starts +# before "10-init-webserver-config" renders its config. s6 restarts the crashed +# services so the container recovers, but it produces alarming errors, a slower +# start, and a brief window with no service. The entrypoint oneshots are chained +# in alphabetical order, so depending on one transitively waits for all earlier +# ones. Each dependency is only added when both the service and the oneshot exist. +add_startup_dependency() { + # $1 = long-running service that must wait, $2 = entrypoint oneshot it needs + service_dir="${S6_HOME}/s6-rc.d/$1" + [ -d "$service_dir" ] && [ -d "${S6_HOME}/s6-rc.d/$2" ] || return 0 + + dependencies_file="${service_dir}/dependencies" + [ -e "$dependencies_file" ] || : > "$dependencies_file" + + # Skip if the dependency is already declared + grep -qxF "$2" "$dependencies_file" 2>/dev/null && return 0 + + # Ensure existing content ends with a newline before appending (some shipped + # dependency files, e.g. nginx's, have no trailing newline) + if [ -s "$dependencies_file" ] && [ -n "$(tail -c 1 "$dependencies_file")" ]; then + printf '\n' >> "$dependencies_file" + fi + printf '%s\n' "$2" >> "$dependencies_file" +} + +add_startup_dependency php-fpm 5-fpm-pool-user +add_startup_dependency nginx 10-init-webserver-config +add_startup_dependency apache2 10-init-webserver-config \ No newline at end of file From 289b4b08e6245085313cffcf83bdc0f531f8b379 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 10 Sep 2026 16:08:46 +0000 Subject: [PATCH 2/2] Migrate shipped services and generated oneshots from the deprecated flat dependencies file to dependencies.d, make every user service depend on base as s6-overlay recommends, and make php-fpm, nginx, and apache2 wait for the oneshots that write their config. Fixes root-mode FPM 'user has not been defined' race (#425, #689). Co-authored-by: LorenzoRogai <1665768+LorenzoRogai@users.noreply.github.com> Co-authored-by: mbrodala <5037116+mbrodala@users.noreply.github.com> --- .../3.adding-your-own-start-up-scripts.md | 8 ++- .../{dependencies => dependencies.d/base} | 0 .../local/bin/docker-php-serversideup-s6-init | 56 +++++++------------ .../s6-overlay/s6-rc.d/apache2/dependencies | 1 - .../s6-rc.d/apache2/dependencies.d/base | 0 .../s6-rc.d/apache2/dependencies.d/php-fpm | 0 .../etc/s6-overlay/s6-rc.d/nginx/dependencies | 1 - .../s6-rc.d/nginx/dependencies.d/base | 0 .../s6-rc.d/nginx/dependencies.d/php-fpm | 0 9 files changed, 27 insertions(+), 39 deletions(-) rename src/s6/etc/s6-overlay/s6-rc.d/php-fpm/{dependencies => dependencies.d/base} (100%) delete mode 100644 src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies create mode 100644 src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies.d/base create mode 100644 src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies.d/php-fpm delete mode 100644 src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies create mode 100644 src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/base create mode 100644 src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm diff --git a/docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md b/docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md index 60b6a0e4d..129d69cf8 100644 --- a/docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md +++ b/docs/content/docs/6.customizing-the-image/3.adding-your-own-start-up-scripts.md @@ -151,6 +151,12 @@ In the above file, we're copying our "one-shot" scripts to the `/etc/entrypoint. The magic happens when we run `docker-php-serversideup-s6-init`. This script will move all our scripts from the `/etc/entrypoint.d` directory to the `/etc/s6-overlay/scripts` directory and set the correct dependencies for our S6 services. -You can now reference our script names as dependencies in your own S6 service. +You can now reference our script names as dependencies in your own S6 service. Declare each dependency as an empty file in your service's `dependencies.d` directory. S6 Overlay recommends every service also depend on `base` to prevent race conditions during container start up. + +```sh +mkdir -p /etc/s6-overlay/s6-rc.d/my-s6-service/dependencies.d +touch /etc/s6-overlay/s6-rc.d/my-s6-service/dependencies.d/base +touch /etc/s6-overlay/s6-rc.d/my-s6-service/dependencies.d/10-init-webserver-config +``` :u-button{to="https://github.com/just-containers/s6-overlay" label="Learn more about S6 Overlay" aria-label="Learn more about S6 Overlay" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500" target="_blank"} diff --git a/src/s6/etc/s6-overlay/s6-rc.d/php-fpm/dependencies b/src/s6/etc/s6-overlay/s6-rc.d/php-fpm/dependencies.d/base similarity index 100% rename from src/s6/etc/s6-overlay/s6-rc.d/php-fpm/dependencies rename to src/s6/etc/s6-overlay/s6-rc.d/php-fpm/dependencies.d/base diff --git a/src/s6/usr/local/bin/docker-php-serversideup-s6-init b/src/s6/usr/local/bin/docker-php-serversideup-s6-init index 6afdf5b6e..4dcfb7fec 100644 --- a/src/s6/usr/local/bin/docker-php-serversideup-s6-init +++ b/src/s6/usr/local/bin/docker-php-serversideup-s6-init @@ -27,6 +27,17 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi +# Services are skipped when they are not part of the image (e.g. nginx on an Apache image) +add_dependency() { + service="$1" + dependency="$2" + + if [ -d "${S6_HOME}/s6-rc.d/${service}" ]; then + mkdir -p "${S6_HOME}/s6-rc.d/${service}/dependencies.d" + touch "${S6_HOME}/s6-rc.d/${service}/dependencies.d/${dependency}" + fi +} + for file in "$ENTRYPOINT_DIR"/*.sh; do [ -e "$file" ] || continue # Skip if no files match @@ -47,6 +58,9 @@ for file in "$ENTRYPOINT_DIR"/*.sh; do # Place empty file in contents.d touch "${S6_HOME}/s6-rc.d/user/contents.d/${script_name}" + # S6 Overlay recommends every user service depend on "base" to prevent race conditions + add_dependency "$script_name" base + # Ensure the ${S6_HOME}/scripts/ directory exists mkdir -p "${S6_HOME}/scripts" @@ -65,10 +79,7 @@ for file in "$ENTRYPOINT_DIR"/*.sh; do # Check if the previous script is not the current script and set as dependency if [ "$previous_script_name" != "$script_name" ] && [ -n "$previous_script_name" ]; then - dependencies_file="${S6_HOME}/s6-rc.d/${script_name}/dependencies" - touch "$dependencies_file" - echo "$previous_script_name" >> "$dependencies_file" - chmod 644 "$dependencies_file" + add_dependency "$script_name" "$previous_script_name" fi # Set the previous file for the next loop @@ -79,35 +90,8 @@ for file in "$ENTRYPOINT_DIR"/*.sh; do done -# Make the long-running services wait for the entrypoint oneshots that configure -# them. When the container runs as root, php-fpm and the web server otherwise -# start in parallel with these oneshots and can lose the race: php-fpm reads the -# pool before "5-fpm-pool-user" adds "user = www-data" (ALERT: [pool www] user -# has not been defined -> FPM initialization failed), and the web server starts -# before "10-init-webserver-config" renders its config. s6 restarts the crashed -# services so the container recovers, but it produces alarming errors, a slower -# start, and a brief window with no service. The entrypoint oneshots are chained -# in alphabetical order, so depending on one transitively waits for all earlier -# ones. Each dependency is only added when both the service and the oneshot exist. -add_startup_dependency() { - # $1 = long-running service that must wait, $2 = entrypoint oneshot it needs - service_dir="${S6_HOME}/s6-rc.d/$1" - [ -d "$service_dir" ] && [ -d "${S6_HOME}/s6-rc.d/$2" ] || return 0 - - dependencies_file="${service_dir}/dependencies" - [ -e "$dependencies_file" ] || : > "$dependencies_file" - - # Skip if the dependency is already declared - grep -qxF "$2" "$dependencies_file" 2>/dev/null && return 0 - - # Ensure existing content ends with a newline before appending (some shipped - # dependency files, e.g. nginx's, have no trailing newline) - if [ -s "$dependencies_file" ] && [ -n "$(tail -c 1 "$dependencies_file")" ]; then - printf '\n' >> "$dependencies_file" - fi - printf '%s\n' "$2" >> "$dependencies_file" -} - -add_startup_dependency php-fpm 5-fpm-pool-user -add_startup_dependency nginx 10-init-webserver-config -add_startup_dependency apache2 10-init-webserver-config \ No newline at end of file +# Long-running services must wait for the oneshots that write their config, otherwise +# they race the oneshots when the container runs as root (see #689). +add_dependency php-fpm 5-fpm-pool-user +add_dependency nginx 10-init-webserver-config +add_dependency apache2 10-init-webserver-config diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies deleted file mode 100644 index 54f9367ca..000000000 --- a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies +++ /dev/null @@ -1 +0,0 @@ -php-fpm \ No newline at end of file diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies.d/base b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies.d/base new file mode 100644 index 000000000..e69de29bb diff --git a/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies.d/php-fpm b/src/variations/fpm-apache/etc/s6-overlay/s6-rc.d/apache2/dependencies.d/php-fpm new file mode 100644 index 000000000..e69de29bb diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies deleted file mode 100644 index 54f9367ca..000000000 --- a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies +++ /dev/null @@ -1 +0,0 @@ -php-fpm \ No newline at end of file diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/base b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/base new file mode 100644 index 000000000..e69de29bb diff --git a/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm b/src/variations/fpm-nginx/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/php-fpm new file mode 100644 index 000000000..e69de29bb