You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bring redis and the cloud-sql-proxy sidecars back to the FrankenPHP dev variants by introducing a non-root supervisord layered on top of the upstream FrankenPHP image. The upstream image (edge-docker-php) stays single-process / PID 1 for serverless production. All changes live in the edge-docker-php-dev repo.
The existing root-supervisord setup that the non-FrankenPHP dev variants inherit from upstream is out of scope — those continue to work unchanged.
Decisions (from clarifying Q&A)
#
Decision
1
Install redis-server apt package in the FrankenPHP dev overlay so supervisord can run it as edge.
2
Redis is always on. No ENABLE_REDIS toggle on the FrankenPHP dev track.
3
FrankenPHP itself becomes a supervisord-managed program. Supervisord (running as edge) is PID 1.
4
Supervisord socket / pidfile / log paths under /tmp so edge owns them and supervisorctl works without sudo.
xdebug on/off keeps using frankenphp reload --force (no change).
7
Cloud SQL proxy: upstream commit 2042f5c only added cloud-sql-proxy to Dockerfile.common, not Dockerfile.common-frankenphp. The dev image previously had its own COPY --from=…cloud-sql-proxy (removed in dev commit 1e406a5). On the FrankenPHP track the binary is currently missing entirely. We re-add the COPY in Dockerfile.common-frankenphp in the dev repo. If upstream FrankenPHP later gains it, the dev COPY becomes a harmless overwrite.
8
sshd is NOT supported on the FrankenPHP dev track. Standard OpenSSH sshd requires root for PAM/privsep + chpasswd; running it under non-root supervisord is contrary to the design goal. Documented as unsupported.
9
cron is NOT supported on the FrankenPHP dev track. Standard Debian cron must start as root (setuids to each crontab's owner). Adding a non-root scheduler (e.g. supercronic) is out of scope for this change. README mentions supercronic as a future option.
10
Cloud SQL proxy is always on. No ENABLE_SQL_PROXY toggle on the FrankenPHP dev track.
11
No templating engine required. Because every program is unconditionally autostart=true, the supervisord conf is fully static. No j2/jinjanator install in the dev fphp overlay; the conf is shipped as a static file in the dev repo and copied into the image at build time. The upstream non-fphp track's j2 usage is unchanged (it has 4 conditional templates and is out of scope).
12
No stopasgroup / killasgroup on any program. The three managed programs (frankenphp, redis-server, cloud-sql-proxy) are single-binary processes that handle SIGTERM cleanly themselves; stopasgroup is meant for things like Flask debug mode or shell wrappers that don't propagate signals. Matches upstream non-fphp supervisord.conf.j2, which also doesn't use these.
13
Supervisord's own log:logfile=/dev/null, logfile_maxbytes=0. With nodaemon=true, supervisord prints its state-change messages (INFO success: frankenphp entered RUNNING state, etc.) to its own stdout, which docker captures. Matches upstream conf exactly.
14
No umask=002 in our conf. Upstream non-fphp uses it because that track is multi-user (nginx, www-data, edge in the edge group). The fphp dev image is single-user (every supervised process runs as edge), so umask=002 has no functional effect. supervisord's default 022 is used.
15
dev.sh is argument-aware. If invoked with no args (the default CMD ["/dev.sh"] case) it falls back to launching supervisord. If invoked with args (e.g. docker run <image> /dev.sh bash) it forwards them through launch.sh. Pattern mirrors launch-frankenphp.sh's own three-branch arg handling.
Current state (recap of investigation)
edge-docker-php (upstream) :*-frankenphp tags:
Single-process model. ENTRYPOINT ["/launch.sh"], no entrypoint shim,
no supervisord, no redis-server, no cloud-sql-proxy,
no openssh-server, no cron, no j2/jinjanator, no sudo.
launch.shexecs frankenphp run --config /etc/caddy/Caddyfile as edge (PID 1).
edge-docker-php (upstream) non-fphp :* tags now ship cloud-sql-proxy
in the base (commit 2042f5c), with a corresponding [program:sql-proxy]
in the upstream templates/supervisord.conf.j2.
edge-docker-php-dev:*-frankenphp tags (current state, post 1e406a5):
Inherit the upstream FrankenPHP base, then CMD ["/dev.sh"] which
finally exec /launch.sh.
The dev image's own COPY --from=…cloud-sql-proxy and overlay etc/supervisor/conf.d/sql-proxy.conf were removed on the assumption
upstream provides them. On the FrankenPHP track that assumption is
currently false — the binary is missing entirely.
edge-docker-php-dev:8.3 / :8.4 tags inherit upstream non-fphp where
supervisord runs as root and forks per-program users (edge, redis, nginx). That is the model we are NOT replicating.
Design
Process model
Supervisord (running as edge) becomes PID 1 of the dev container and
supervises three programs, all autostart=true, all running as edge:
Program
Command
frankenphp
/usr/local/bin/frankenphp run --config /etc/caddy/Caddyfile
/usr/local/bin/cloud-sql-proxy (binary copied via dev overlay — see decision #7)
sshd and cron are intentionally not managed (decisions #8, #9).
The README documents ENABLE_SSH and ENABLE_CRON as unsupported on the
FrankenPHP track. ENABLE_REDIS and ENABLE_SQL_PROXY are likewise
unsupported on this track (decisions #2, #10) — both services are always
on. Users wanting toggleable services should use the non-fphp dev variants.
All programs:
stdout_logfile=/dev/stdout, stderr_logfile=/dev/stderr, *_logfile_maxbytes=0 (so logs flow to docker logs).
user=edge in [supervisord] is harmless when supervisord is launched
as edge and prevents accidental escalation.
SERVER_NAME and SERVER_ROOT env vars are computed by the launcher
before exec'ing supervisord; supervisord inherits and passes them to
frankenphp via the normal child-process environment (no environment=
line needed since the values are in the supervisord process's env).
cloud-sql-proxy invocation matches upstream non-fphp's stanza exactly
(users supply DB instance config via env vars / extra args at deploy
time, same as today).
Logging behavior
Child program output (frankenphp, redis, sql-proxy stdout+stderr) is
routed by supervisord directly to /dev/stdout and /dev/stderr of
the supervisord (PID 1) process — docker logs captures both streams.
Supervisord's own activity log (state changes, restart notices)
goes to its own stdout because nodaemon=true and silent is left at
its default false. logfile=/dev/null + logfile_maxbytes=0
suppresses any file logging and is required because /dev/null is not
seekable (per the supervisor docs for non-seekable logfile paths).
Net effect: identical to the upstream non-fphp track — all useful logs
appear in docker logs.
Boot sequence
We deliberately do not add a second launcher script that duplicates
the env-setup logic from launch-frankenphp.sh. The upstream launcher
already supports invoking arbitrary commands as PID 1 — its third arg
branch is exec "$@". We exploit that.
The new chain on the FrankenPHP dev variants:
ENTRYPOINT ["/launch.sh"] — unchanged (inherited from upstream).
CMD ["/dev.sh"] — unchanged at the Docker level.
So the runtime invocation is /launch.sh /dev.sh — launch.sh sources /etc/profile.d/edge-env.sh, exports SERVER_NAME and SERVER_ROOT,
then execs /dev.sh (its third-branch exec "$@").
Writes $RUNTIME_URL to /tmp/runtime.url (existing).
Toggles xdebug if XDEBUG_ENABLE=On (existing).
CHANGED: the existing final exec /launch.sh "$@" is replaced
by an arg-aware pattern that mirrors launch-frankenphp.sh's own
fallback model:
# If no args were passed, default to supervisord-managed multi-proc.# Otherwise, forward whatever was passed through launch.sh.if [ $#-eq 0 ];thenset -- /usr/bin/supervisord
fiexec /launch.sh "$@"
This re-enters launch.sh with explicit args. launch.sh's env exports
are idempotent (edge-env.sh source is guarded by CUSTOM_VARS_SET; SERVER_NAME / SERVER_ROOT re-exports are no-ops). It then execs
whatever was selected, which becomes PID 1 with all required env in
scope.
Container PID 1 in the default case is then supervisord running as edge. No new launcher script lives in the dev repo, no upstream
changes are needed.
Invocation modes
The arg-aware dev.sh gives users three boot modes without sacrificing the
single-process escape hatch:
Invocation
What happens
docker run <image>
CMD /dev.sh runs with no args → dev.sh setup → defaults to supervisord (multi-proc, all sidecars).
docker run <image> /dev.sh frankenphp run --config /etc/caddy/Caddyfile
Bypasses dev.sh entirely (CMD overridden) — identical to upstream behavior.
Why this is safe to run as edge
All sockets / pidfiles / log paths are under /tmp (writable).
All managed programs already run fine as edge: frankenphp does so
upstream; cloud-sql-proxy is a userland Go binary; redis-server
doesn't need a dedicated redis user when its --dir is a writable
path (we use /tmp).
No CAP_NET_BIND_SERVICE needed — frankenphp listens on ${PORT}
(default 8080), redis on 127.0.0.1:6379, sql-proxy on >1024 ports.
Shutdown semantics
docker stop sends SIGTERM to PID 1 (supervisord) → supervisord sends
SIGTERM to each child → each child handles it cleanly (frankenphp
drains, redis exits, sql-proxy exits).
Ona stop: for the FrankenPHP track: supervisorctl shutdown (no -c flag needed because /etc/supervisord.conf is in supervisorctl's
default search path; no sudo needed because supervisord runs as edge and the socket is under /tmp).
xdebug toggle (no change to user behavior)
usr/local/bin/xdebug already detects frankenphp and uses frankenphp reload --force --config /etc/caddy/Caddyfile. That keeps
working unchanged because frankenphp is still running with the same
config file — supervisord just supervises the process now.
File-by-file changes (in /workspaces/edge-docker-php-dev)
Add
etc/supervisord.conf — new static file, contents shown above.
(Path chosen to match the upstream non-fphp track which writes its
rendered conf to /etc/supervisord.conf. Lives under etc/ in the
repo so the existing COPY --chown=edge . / lands it at /etc/supervisord.conf.)
Modify
Dockerfile.common-frankenphp:
Add to apt install list (root section): redis-server, supervisor.
(No pipx/jinjanator — decision #11.)
Re-introduce the cloud-sql-proxy COPY (decision Frankenphp super #7) in the root
section, before the final USER edge:
# Install Google Cloud SQL Proxy (upstream FrankenPHP base does not ship it)COPY --from=gcr.io/cloud-sql-connectors/cloud-sql-proxy:2 \
/cloud-sql-proxy /usr/local/bin/cloud-sql-proxy
dev.sh:
Replace the final exec /launch.sh "$@" with the arg-aware
fallback pattern (decision #15):
if [ $#-eq 0 ];thenset -- /usr/bin/supervisord
fiexec /launch.sh "$@"
This routes through the existing upstream launcher (which sets up SERVER_NAME / SERVER_ROOT / edge-env.sh and then execs the
command per its third arg-handling branch). No new launcher script
is needed in the dev repo.
README.md:
Update the "Image variants" table — the FrankenPHP variants now use
"supervisord (multi-proc, runs as edge)" instead of "single process".
Add a new section that outlines the "Invocation modes" mentioned above (the table with Invocation and What happens).
Replace the FrankenPHP Ona automations.yaml example to use a
supervisord-style start/stop:
Reflect that redis and cloud-sql-proxy now run on the FrankenPHP
track too (always on, no env toggle).
Document explicitly that ENABLE_REDIS, ENABLE_SQL_PROXY, ENABLE_SSH, and ENABLE_CRON are unsupported on the FrankenPHP
track. Users needing toggles or sshd/cron should use the
non-frankenphp dev variants. Mention supercronic as a future option
for non-root cron if demand arises.
Unchanged
Dockerfile.common (non-fphp dev) — out of scope.
Dockerfile.php83, Dockerfile.php84 — out of scope.
Dockerfile.php83-frankenphp, Dockerfile.php84-frankenphp — they
only INCLUDE+ Dockerfile.common-frankenphp, no edits needed.
usr/local/bin/xdebug — already handles both tracks correctly.
Expect: supervisord starts as edge, all three child programs
log to docker stdout. Supervisord's own INFO success: … entered RUNNING state lines visible too.
curl localhost:8080/healthz → ok.
docker exec -it <c> supervisorctl status (no sudo) → all three
(frankenphp, redis, sql-proxy) RUNNING.
docker exec -it <c> redis-cli ping → PONG.
docker exec -it <c> which cloud-sql-proxy → /usr/local/bin/cloud-sql-proxy (verifies the re-introduced COPY).
docker exec -it <c> xdebug on → "Xdebug enabled", frankenphp
reloads, PHP exposes xdebug; xdebug off reverses it.
docker exec <c> ps -eo user,pid,cmd → all processes are edge,
no stray root processes.
docker stop <c> exits within the grace period; logs show clean
shutdown messages from all three children.
docker run --rm -p 8080:8080 outeredge/edge-docker-php:8.3-frankenphp
(upstream) still PID-1-frankenphp, regression-free.
Invocation-mode checks (decision #15):
docker run --rm -p 8080:8080 test:fphp /dev.sh frankenphp run --config /etc/caddy/Caddyfile
→ dev.sh setup runs (composer caches set, xdebug toggled per env,
etc.) then exec's single-proc frankenphp. ps shows frankenphp
as PID 1, no supervisord.
docker run --rm -it test:fphp /dev.sh bash → dev.sh setup runs,
drops to interactive shell. id is edge.
docker run --rm test:fphp bash -c 'echo $XDEBUG_ENABLE' →
bypasses dev.sh entirely (CMD overridden, dev.sh setup skipped). XDEBUG_ENABLE reads from the image default Off. Confirms the
pure escape hatch is intact.
Risks / open considerations
redis-server writes to /tmp: by design — dev redis is
ephemeral. If a user wants persistence they can edit /etc/supervisord.conf to override --dir.
Escape hatch for single-process behavior: still works via docker run … <image> frankenphp run --config /etc/caddy/Caddyfile
(or any other CMD override). The supervisord-managed multi-proc
behavior only engages when dev.sh runs and reaches its final exec.
Goal
Bring
redisand thecloud-sql-proxysidecars back to the FrankenPHP dev variants by introducing a non-root supervisord layered on top of the upstream FrankenPHP image. The upstream image (edge-docker-php) stays single-process / PID 1 for serverless production. All changes live in theedge-docker-php-devrepo.The existing root-supervisord setup that the non-FrankenPHP dev variants inherit from upstream is out of scope — those continue to work unchanged.
Decisions (from clarifying Q&A)
redis-serverapt package in the FrankenPHP dev overlay so supervisord can run it asedge.ENABLE_REDIStoggle on the FrankenPHP dev track.edge) is PID 1./tmpsoedgeowns them andsupervisorctlworks without sudo.xdebug on/offkeeps usingfrankenphp reload --force(no change).2042f5conly addedcloud-sql-proxytoDockerfile.common, notDockerfile.common-frankenphp. The dev image previously had its ownCOPY --from=…cloud-sql-proxy(removed in dev commit1e406a5). On the FrankenPHP track the binary is currently missing entirely. We re-add the COPY inDockerfile.common-frankenphpin the dev repo. If upstream FrankenPHP later gains it, the devCOPYbecomes a harmless overwrite.sshdis NOT supported on the FrankenPHP dev track. Standard OpenSSHsshdrequires root for PAM/privsep +chpasswd; running it under non-root supervisord is contrary to the design goal. Documented as unsupported.cronis NOT supported on the FrankenPHP dev track. Standard Debiancronmust start as root (setuids to each crontab's owner). Adding a non-root scheduler (e.g. supercronic) is out of scope for this change. README mentions supercronic as a future option.ENABLE_SQL_PROXYtoggle on the FrankenPHP dev track.autostart=true, the supervisord conf is fully static. Noj2/jinjanator install in the dev fphp overlay; the conf is shipped as a static file in the dev repo and copied into the image at build time. The upstream non-fphp track'sj2usage is unchanged (it has 4 conditional templates and is out of scope).stopasgroup/killasgroupon any program. The three managed programs (frankenphp, redis-server, cloud-sql-proxy) are single-binary processes that handle SIGTERM cleanly themselves;stopasgroupis meant for things like Flask debug mode or shell wrappers that don't propagate signals. Matches upstream non-fphpsupervisord.conf.j2, which also doesn't use these.logfile=/dev/null,logfile_maxbytes=0. Withnodaemon=true, supervisord prints its state-change messages (INFO success: frankenphp entered RUNNING state, etc.) to its own stdout, which docker captures. Matches upstream conf exactly.umask=002in our conf. Upstream non-fphp uses it because that track is multi-user (nginx, www-data, edge in the edge group). The fphp dev image is single-user (every supervised process runs asedge), soumask=002has no functional effect. supervisord's default022is used.dev.shis argument-aware. If invoked with no args (the defaultCMD ["/dev.sh"]case) it falls back to launching supervisord. If invoked with args (e.g.docker run <image> /dev.sh bash) it forwards them throughlaunch.sh. Pattern mirrorslaunch-frankenphp.sh's own three-branch arg handling.Current state (recap of investigation)
edge-docker-php(upstream):*-frankenphptags:ENTRYPOINT ["/launch.sh"], no entrypoint shim,no supervisord, no redis-server, no
cloud-sql-proxy,no
openssh-server, nocron, noj2/jinjanator, nosudo.launch.shexecsfrankenphp run --config /etc/caddy/Caddyfileasedge(PID 1).edge-docker-php(upstream) non-fphp:*tags now shipcloud-sql-proxyin the base (commit
2042f5c), with a corresponding[program:sql-proxy]in the upstream
templates/supervisord.conf.j2.edge-docker-php-dev:*-frankenphptags (current state, post1e406a5):CMD ["/dev.sh"]whichfinally
exec /launch.sh.COPY --from=…cloud-sql-proxyand overlayetc/supervisor/conf.d/sql-proxy.confwere removed on the assumptionupstream provides them. On the FrankenPHP track that assumption is
currently false — the binary is missing entirely.
edge-docker-php-dev:8.3/:8.4tags inherit upstream non-fphp wheresupervisord runs as root and forks per-program users (
edge,redis,nginx). That is the model we are NOT replicating.Design
Process model
Supervisord (running as
edge) becomes PID 1 of the dev container andsupervises three programs, all
autostart=true, all running asedge:frankenphp/usr/local/bin/frankenphp run --config /etc/caddy/Caddyfileredis/usr/bin/redis-server --save "" --dir /tmp --appendonly no --bind 127.0.0.1 --port 6379sql-proxy/usr/local/bin/cloud-sql-proxy(binary copied via dev overlay — see decision #7)sshdandcronare intentionally not managed (decisions #8, #9).The README documents
ENABLE_SSHandENABLE_CRONas unsupported on theFrankenPHP track.
ENABLE_REDISandENABLE_SQL_PROXYare likewiseunsupported on this track (decisions #2, #10) — both services are always
on. Users wanting toggleable services should use the non-fphp dev variants.
All programs:
stdout_logfile=/dev/stdout,stderr_logfile=/dev/stderr,*_logfile_maxbytes=0(so logs flow todocker logs).autorestart=true(matching upstream supervisord patterns).stopasgroup/killasgroup(decision #12).Supervisord configuration
A static, hand-written file shipped at
etc/supervisord.confin the devrepo, copied to
/etc/supervisord.confat build time (owned byedge):Notes:
user=edgein[supervisord]is harmless when supervisord is launchedas
edgeand prevents accidental escalation.SERVER_NAMEandSERVER_ROOTenv vars are computed by the launcherbefore exec'ing supervisord; supervisord inherits and passes them to
frankenphp via the normal child-process environment (no
environment=line needed since the values are in the supervisord process's env).
cloud-sql-proxyinvocation matches upstream non-fphp's stanza exactly(users supply DB instance config via env vars / extra args at deploy
time, same as today).
Logging behavior
routed by supervisord directly to
/dev/stdoutand/dev/stderrofthe supervisord (PID 1) process —
docker logscaptures both streams.goes to its own stdout because
nodaemon=trueandsilentis left atits default
false.logfile=/dev/null+logfile_maxbytes=0suppresses any file logging and is required because
/dev/nullis notseekable (per the supervisor docs for non-seekable logfile paths).
appear in
docker logs.Boot sequence
We deliberately do not add a second launcher script that duplicates
the env-setup logic from
launch-frankenphp.sh. The upstream launcheralready supports invoking arbitrary commands as PID 1 — its third arg
branch is
exec "$@". We exploit that.The new chain on the FrankenPHP dev variants:
ENTRYPOINT ["/launch.sh"]— unchanged (inherited from upstream).CMD ["/dev.sh"]— unchanged at the Docker level./launch.sh /dev.sh—launch.shsources/etc/profile.d/edge-env.sh, exportsSERVER_NAMEandSERVER_ROOT,then
execs/dev.sh(its third-branchexec "$@").dev.sh:COMPOSER_HOME,YARN_CACHE_FOLDER,npm_config_cache(existing)./ona.shifgitpodis on PATH (existing).$RUNTIME_URLto/tmp/runtime.url(existing).XDEBUG_ENABLE=On(existing).exec /launch.sh "$@"is replacedby an arg-aware pattern that mirrors
launch-frankenphp.sh's ownfallback model:
are idempotent (
edge-env.shsource is guarded byCUSTOM_VARS_SET;SERVER_NAME/SERVER_ROOTre-exports are no-ops). It thenexecswhatever was selected, which becomes PID 1 with all required env in
scope.
Container PID 1 in the default case is then
supervisordrunning asedge. No new launcher script lives in the dev repo, no upstreamchanges are needed.
Invocation modes
The arg-aware dev.sh gives users three boot modes without sacrificing the
single-process escape hatch:
docker run <image>/dev.shruns with no args → dev.sh setup → defaults to supervisord (multi-proc, all sidecars).docker run <image> /dev.sh frankenphp run --config /etc/caddy/Caddyfileexecs frankenphp directly (single-proc).docker run <image> /dev.sh bashdocker run <image> bashWhy this is safe to run as
edge/tmp(writable).edge: frankenphp does soupstream; cloud-sql-proxy is a userland Go binary;
redis-serverdoesn't need a dedicated
redisuser when its--diris a writablepath (we use
/tmp).CAP_NET_BIND_SERVICEneeded — frankenphp listens on${PORT}(default 8080), redis on 127.0.0.1:6379, sql-proxy on >1024 ports.
Shutdown semantics
docker stopsends SIGTERM to PID 1 (supervisord) → supervisord sendsSIGTERM to each child → each child handles it cleanly (frankenphp
drains, redis exits, sql-proxy exits).
stop:for the FrankenPHP track:supervisorctl shutdown(no-cflag needed because/etc/supervisord.confis in supervisorctl'sdefault search path; no
sudoneeded because supervisord runs asedgeand the socket is under/tmp).xdebug toggle (no change to user behavior)
usr/local/bin/xdebugalready detectsfrankenphpand usesfrankenphp reload --force --config /etc/caddy/Caddyfile. That keepsworking unchanged because frankenphp is still running with the same
config file — supervisord just supervises the process now.
File-by-file changes (in
/workspaces/edge-docker-php-dev)Add
etc/supervisord.conf— new static file, contents shown above.(Path chosen to match the upstream non-fphp track which writes its
rendered conf to
/etc/supervisord.conf. Lives underetc/in therepo so the existing
COPY --chown=edge . /lands it at/etc/supervisord.conf.)Modify
Dockerfile.common-frankenphp:redis-server,supervisor.(No
pipx/jinjanator— decision #11.)section, before the final
USER edge:dev.sh:exec /launch.sh "$@"with the arg-awarefallback pattern (decision #15):
SERVER_NAME/SERVER_ROOT/edge-env.shand thenexecs thecommand per its third arg-handling branch). No new launcher script
is needed in the dev repo.
README.md:"supervisord (multi-proc, runs as
edge)" instead of "single process".automations.yamlexample to use asupervisord-style start/stop:
redisandcloud-sql-proxynow run on the FrankenPHPtrack too (always on, no env toggle).
ENABLE_REDIS,ENABLE_SQL_PROXY,ENABLE_SSH, andENABLE_CRONare unsupported on the FrankenPHPtrack. Users needing toggles or sshd/cron should use the
non-frankenphp dev variants. Mention supercronic as a future option
for non-root cron if demand arises.
Unchanged
Dockerfile.common(non-fphp dev) — out of scope.Dockerfile.php83,Dockerfile.php84— out of scope.Dockerfile.php83-frankenphp,Dockerfile.php84-frankenphp— theyonly
INCLUDE+ Dockerfile.common-frankenphp, no edits needed.usr/local/bin/xdebug— already handles both tracks correctly.home/edge/.bashrc,home/edge/.bash_profile,templates/20-xdebug.ini,ona.sh,publish.sh.edge-docker-phprepo — untouched. Productionsingle-process semantics preserved. The existing
launch-frankenphp.shthird arg-handling branch (
exec "$@") already accommodates ourpattern; no upstream tweak required.
Verification plan (post-implementation, manual)
docker build -f Dockerfile.php83-frankenphp -t test:fphp .docker run --rm -p 8080:8080 test:fphpedge, all three child programslog to docker stdout. Supervisord's own
INFO success: … entered RUNNING statelines visible too.curl localhost:8080/healthz→ok.docker exec -it <c> supervisorctl status(no sudo) → all three(
frankenphp,redis,sql-proxy) RUNNING.docker exec -it <c> redis-cli ping→PONG.docker exec -it <c> which cloud-sql-proxy→/usr/local/bin/cloud-sql-proxy(verifies the re-introduced COPY).docker exec -it <c> xdebug on→ "Xdebug enabled", frankenphpreloads, PHP exposes xdebug;
xdebug offreverses it.docker exec <c> ps -eo user,pid,cmd→ all processes areedge,no stray root processes.
docker stop <c>exits within the grace period; logs show cleanshutdown messages from all three children.
docker run --rm -p 8080:8080 outeredge/edge-docker-php:8.3-frankenphp(upstream) still PID-1-frankenphp, regression-free.
docker run --rm -p 8080:8080 test:fphp /dev.sh frankenphp run --config /etc/caddy/Caddyfile→ dev.sh setup runs (composer caches set, xdebug toggled per env,
etc.) then exec's single-proc frankenphp.
psshows frankenphpas PID 1, no supervisord.
docker run --rm -it test:fphp /dev.sh bash→ dev.sh setup runs,drops to interactive shell.
idisedge.docker run --rm test:fphp bash -c 'echo $XDEBUG_ENABLE'→bypasses dev.sh entirely (CMD overridden, dev.sh setup skipped).
XDEBUG_ENABLEreads from the image defaultOff. Confirms thepure escape hatch is intact.
Risks / open considerations
redis-serverwrites to/tmp: by design — dev redis isephemeral. If a user wants persistence they can edit
/etc/supervisord.confto override--dir.docker run … <image> frankenphp run --config /etc/caddy/Caddyfile(or any other CMD override). The supervisord-managed multi-proc
behavior only engages when
dev.shruns and reaches its final exec.