Skip to content

Commit c7befe0

Browse files
Lorenzo Rogaiclaude
andcommitted
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) <noreply@anthropic.com>
1 parent 49039c2 commit c7befe0

1 file changed

Lines changed: 34 additions & 1 deletion

File tree

src/s6/usr/local/bin/docker-php-serversideup-s6-init

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,37 @@ for file in "$ENTRYPOINT_DIR"/*.sh; do
7777
echo "Skipping ${script_name} because it already exists at ${S6_HOME}/scripts/${script_name}"
7878
fi
7979

80-
done
80+
done
81+
82+
# Make the long-running services wait for the entrypoint oneshots that configure
83+
# them. When the container runs as root, php-fpm and the web server otherwise
84+
# start in parallel with these oneshots and can lose the race: php-fpm reads the
85+
# pool before "5-fpm-pool-user" adds "user = www-data" (ALERT: [pool www] user
86+
# has not been defined -> FPM initialization failed), and the web server starts
87+
# before "10-init-webserver-config" renders its config. s6 restarts the crashed
88+
# services so the container recovers, but it produces alarming errors, a slower
89+
# start, and a brief window with no service. The entrypoint oneshots are chained
90+
# in alphabetical order, so depending on one transitively waits for all earlier
91+
# ones. Each dependency is only added when both the service and the oneshot exist.
92+
add_startup_dependency() {
93+
# $1 = long-running service that must wait, $2 = entrypoint oneshot it needs
94+
service_dir="${S6_HOME}/s6-rc.d/$1"
95+
[ -d "$service_dir" ] && [ -d "${S6_HOME}/s6-rc.d/$2" ] || return 0
96+
97+
dependencies_file="${service_dir}/dependencies"
98+
[ -e "$dependencies_file" ] || : > "$dependencies_file"
99+
100+
# Skip if the dependency is already declared
101+
grep -qxF "$2" "$dependencies_file" 2>/dev/null && return 0
102+
103+
# Ensure existing content ends with a newline before appending (some shipped
104+
# dependency files, e.g. nginx's, have no trailing newline)
105+
if [ -s "$dependencies_file" ] && [ -n "$(tail -c 1 "$dependencies_file")" ]; then
106+
printf '\n' >> "$dependencies_file"
107+
fi
108+
printf '%s\n' "$2" >> "$dependencies_file"
109+
}
110+
111+
add_startup_dependency php-fpm 5-fpm-pool-user
112+
add_startup_dependency nginx 10-init-webserver-config
113+
add_startup_dependency apache2 10-init-webserver-config

0 commit comments

Comments
 (0)