frankenphp: wire up the env levers the README promised #16

Merged
geusebio merged 1 commit from claude/frankenphp-overview-9b94c7 into main 2026-08-04 09:56:46 +00:00
Owner

The README's config table listed PHP_CLI_MEMORY_LIMIT and every
PHP_DISABLE_OPCACHE_* variable as applying to frankenphp. None of them
were implemented — the service script only handled memory_limit, error
reporting, display_errors and xdebug, and the image shipped no opcache
config of its own to rewrite.

Levers

  • conf.d/zz_opcache.ini — our own opcache config, mirroring the one the
    php image ships. Named to sort after the upstream php-zts opcache.ini
    (whose directives are all commented out) so ours wins. The service rewrites
    it from PHP_DISABLE_OPCACHE_FILE_CACHE, _JIT and _VALIDATE_TIMESTAMPS.
  • JIT is off by default on 8.2 and 8.3. Their ZTS builds segfault on the
    first request with opcache.jit=tracing; 8.4 and 8.5 serve fine. Verified
    by hand on all four. PHP_DISABLE_OPCACHE_JIT still overrides either way.
  • PHP_CLI_MEMORY_LIMIT now does something. FrankenPHP and the php
    binary share /etc/php-zts/php.ini, so PHPRC points the CLI at its own
    copy. (The same variable in the php image edits /etc/php/php-cli.ini,
    which nothing reads — a separate bug, not touched here.)

Mercure

The service boots from /etc/frankenphp/Caddyfile instead of
frankenphp php-server, which has nowhere to hang extra handlers.
FRANKENPHP_MERCURE=On writes a snippet into the conf.d/*.caddy the
Caddyfile imports; off removes it again. FRANKENPHP_MERCURE_ANONYMOUS=On
allows subscribers without a JWT. Keys come from MERCURE_PUBLISHER_JWT_KEY
/ MERCURE_SUBSCRIBER_JWT_KEY, with a loudly-logged throwaway key if you
don't set one.

Confirmed locally on 8.5: off → text/html from the app, on+anonymous →
text/event-stream from the hub, on without anonymous → 401.

Tests

The frankenphp services were commented out of the test service's
depends_on, so nothing ran them. They now hang off a test-frankenphp
aggregator — ./test test-frankenphp — covering all four versions'
assertions and healthchecks, plus two new ones: a PHP_DEBUG_MODE=On boot
and a FRANKENPHP_MERCURE=On boot. The whole group passes locally on
aarch64.

CI still cannot run them: the pkg.henderkes.com binaries are built with
x86-64-v3/v4 code (readelf -nx86 ISA used: … x86-64-v3, x86-64-v4),
which is why they Illegal instruction on the runner but work on native
aarch64. The group therefore stays out of the default test deps, with the
reason recorded in test.yml rather than four commented-out blocks.

Worth a separate look: the same ISA problem applies to the frankenphp
amd64 images we publish to Docker Hub. Those builds only pass CI because
buildx runs them under QEMU.

Also wired up php-84-debug-healthchecks and php-83-debug-healthchecks,
which were defined in test-healthchecks.yml but not depended on by
anything.

validate.d/frankenphp.sh now parses the Caddyfile and asserts the Mercure
module is in the binary, so config breakage fails the build. Dropped the
nginx holding page — this image has no nginx and serves /app/public.

🤖 Generated with Claude Code

The README's config table listed `PHP_CLI_MEMORY_LIMIT` and every `PHP_DISABLE_OPCACHE_*` variable as applying to `frankenphp`. None of them were implemented — the service script only handled memory_limit, error reporting, display_errors and xdebug, and the image shipped no opcache config of its own to rewrite. ### Levers - **`conf.d/zz_opcache.ini`** — our own opcache config, mirroring the one the `php` image ships. Named to sort after the upstream `php-zts` `opcache.ini` (whose directives are all commented out) so ours wins. The service rewrites it from `PHP_DISABLE_OPCACHE_FILE_CACHE`, `_JIT` and `_VALIDATE_TIMESTAMPS`. - **JIT is off by default on 8.2 and 8.3.** Their ZTS builds segfault on the first request with `opcache.jit=tracing`; 8.4 and 8.5 serve fine. Verified by hand on all four. `PHP_DISABLE_OPCACHE_JIT` still overrides either way. - **`PHP_CLI_MEMORY_LIMIT` now does something.** FrankenPHP and the `php` binary share `/etc/php-zts/php.ini`, so `PHPRC` points the CLI at its own copy. (The same variable in the `php` image edits `/etc/php/php-cli.ini`, which nothing reads — a separate bug, not touched here.) ### Mercure The service boots from `/etc/frankenphp/Caddyfile` instead of `frankenphp php-server`, which has nowhere to hang extra handlers. `FRANKENPHP_MERCURE=On` writes a snippet into the `conf.d/*.caddy` the Caddyfile imports; off removes it again. `FRANKENPHP_MERCURE_ANONYMOUS=On` allows subscribers without a JWT. Keys come from `MERCURE_PUBLISHER_JWT_KEY` / `MERCURE_SUBSCRIBER_JWT_KEY`, with a loudly-logged throwaway key if you don't set one. Confirmed locally on 8.5: off → `text/html` from the app, on+anonymous → `text/event-stream` from the hub, on without anonymous → 401. ### Tests The frankenphp services were commented out of the `test` service's `depends_on`, so **nothing ran them**. They now hang off a `test-frankenphp` aggregator — `./test test-frankenphp` — covering all four versions' assertions and healthchecks, plus two new ones: a `PHP_DEBUG_MODE=On` boot and a `FRANKENPHP_MERCURE=On` boot. The whole group passes locally on aarch64. CI still cannot run them: the `pkg.henderkes.com` binaries are built with x86-64-v3/v4 code (`readelf -n` → `x86 ISA used: … x86-64-v3, x86-64-v4`), which is why they `Illegal instruction` on the runner but work on native aarch64. The group therefore stays out of the default `test` deps, with the reason recorded in `test.yml` rather than four commented-out blocks. **Worth a separate look:** the same ISA problem applies to the `frankenphp` amd64 images we publish to Docker Hub. Those builds only pass CI because buildx runs them under QEMU. Also wired up `php-84-debug-healthchecks` and `php-83-debug-healthchecks`, which were defined in `test-healthchecks.yml` but not depended on by anything. `validate.d/frankenphp.sh` now parses the Caddyfile and asserts the Mercure module is in the binary, so config breakage fails the build. Dropped the nginx holding page — this image has no nginx and serves `/app/public`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
frankenphp: wire up the env levers the README promised
All checks were successful
Build / Run container tests (push) Successful in 10m57s
Build / Build (push) Successful in 31m11s
25aba96e94
The README documented PHP_CLI_MEMORY_LIMIT and the PHP_DISABLE_OPCACHE_*
variables as applying to frankenphp, but the service script implemented
none of them and the image shipped no opcache config of its own.

- Ship conf.d/zz_opcache.ini, named to sort after the upstream php-zts
  opcache.ini so it wins, and rewrite it from PHP_DISABLE_OPCACHE_FILE_CACHE,
  _JIT and _VALIDATE_TIMESTAMPS the way the php image does.
- JIT defaults off on 8.2/8.3: their ZTS builds segfault on the first
  request with it enabled. 8.4 and 8.5 are fine and keep it.
- PHPRC gives the CLI its own php.ini copy, so PHP_CLI_MEMORY_LIMIT is a
  real knob rather than a documented no-op.
- Boot from /etc/frankenphp/Caddyfile instead of `php-server`, which has
  nowhere to hang extra handlers, and add FRANKENPHP_MERCURE=On to serve a
  Mercure hub at /.well-known/mercure. FRANKENPHP_MERCURE_ANONYMOUS opts
  into subscribers without a JWT; keys come from MERCURE_*_JWT_KEY.
- validate.d now parses the Caddyfile and checks the Mercure module is in
  the binary, so a broken config fails the build, not someone's runtime.
- Drop the nginx holding page: this image has no nginx and serves
  /app/public.

Tests: the frankenphp services were commented out of the test service's
dependency list, so nothing ran them. They now hang off a test-frankenphp
aggregator (`./test test-frankenphp`) covering all four versions plus new
debug-mode and Mercure boots. CI still cannot run them - the php-zts
binaries use x86-64-v3/v4 code and SIGILL on the runner - so the group
stays out of the default `test` deps, with the reason recorded. Also
wired up php-8{4,3}-debug-healthchecks, which were defined but orphaned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
Owner

Split the amd64 ISA problem out into #19 — it is a shipping-images problem, not just a testing one, and it wants a decision (lean on upstream / build our own / drop the arch) rather than anything this PR can fix.

Split the amd64 ISA problem out into #19 — it is a shipping-images problem, not just a testing one, and it wants a decision (lean on upstream / build our own / drop the arch) rather than anything this PR can fix.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Grey.ooo/docker!16
No description provided.