FrankenPHP
FrankenPHP is an optional PHP runtime on StoreFrame, a single-binary server with persistent worker mode.
FrankenPHP is a PHP application server built on top of Caddy. It replaces the traditional PHP-FPM process manager with a persistent worker model. New environments start on PHP-FPM. FrankenPHP is available as an alternative (currently in beta); switch with the PHP Runtime utility described below.
Why FrankenPHP instead of PHP-FPM
PHP-FPM works by spawning a pool of worker processes. Each request is handed to an idle worker, which boots the Magento framework (autoloader, DI container, module registration), executes the request, and then either recycles or dies. On a large Magento installation, framework bootstrap alone takes 50–200 ms. Under steady load, that overhead is unavoidable with FPM because bootstrap happens per-request per-worker-cycle.
FrankenPHP's worker mode runs your application in persistent PHP workers. Worker mode needs the opengento/magento2-frankenphp-base module in your code base; the platform turns it on automatically when it finds the module. Without it, FrankenPHP serves each request the classic way. Magento boots once per worker at startup. Incoming requests are dispatched to the already-booted worker, which processes the request and returns to waiting — without re-bootstrapping. The result is consistently lower per-request latency and higher throughput on the same hardware.
Worker mode and the worker script
When the php container starts, it launches FrankenPHP with a Caddy-based configuration. The Caddyfile sets num_threads to four times the VM's vCPU count — this is the size of FrankenPHP's PHP thread pool, the number of PHP requests it can execute at the same time.
When worker mode is active (the module is detected in your code base), the Caddyfile registers three persistent workers:
pub/index.php— the main Magento application, one worker per vCPU.pub/get.php— media files that are not yet on disk.pub/static.php— static files that still need to be generated.
Existing static and media files are served directly by the file server without PHP. Without the module, FrankenPHP runs every request the classic way.
Workers are recycled after 1,000 requests to limit memory growth. (Under PHP-FPM, pm.max_requests is 500.)
PHP version
FrankenPHP images are versioned by PHP version. The PHP version is pinned per Magento release:
| Magento release | PHP version |
|---|---|
| 2.4.9 | 8.4 |
| 2.4.8 | 8.4 |
| 2.4.7 | 8.3 |
| 2.4.6 | 8.2 |
| Mage-OS 3.x | 8.4 |
FrankenPHP images start at PHP 8.2. Releases that use an older PHP version (2.4.5 and earlier) run on PHP-FPM.
StoreFrame builds and maintains its own PHP images tagged as storeframe/php:<version>-frankenphp (and storeframe/php:<version>-fpm for PHP-FPM). The image is selected automatically when you provision an environment based on the Magento version.
Memory and OPcache
PHP memory and OPcache are auto-tuned at provision time. The defaults are generous for Magento:
| Setting | Default | Override key |
|---|---|---|
memory_limit | 2048 MB | PHP__PHP_MEMORY_LIMIT |
opcache.memory_consumption | 2048 MB | PHP__PHP_OPCACHE_MEMORY |
opcache.max_accelerated_files | 200,000 | — |
opcache.validate_timestamps | 1 (checked every 4 s) | — |
max_execution_time | 7200 s | — |
upload_max_filesize / post_max_size | 256 MB | — |
OPcache timestamp validation is enabled with opcache.revalidate_freq=4: PHP checks a cached file for changes at most every 4 seconds, so changed code is picked up without restarting the container.
The PHP worker count (pm.max_children) is calculated from VM memory:
pm.max_children = min(
vCPUs × 8, # CPU ceiling
max((RAM × 35%) / avg_worker_mb, 6) # memory floor
)The avg_worker_mb defaults to 384 MB (measured Magento average with a safety margin). You can lower it if your modules are lighter:
PHP__PHP_AVG_WORKER_MB='256'MariaDB is allocated 30% of RAM, PHP workers 35%, leaving the remainder for OpenSearch, Redis, the OS page cache, and other containers. These ratios are coordinated — adjusting one affects the others.
HTTP/3
HTTP/3 (QUIC) is served by the nginx (OpenResty) container, which sits in front of the PHP container and handles all inbound traffic — including TLS termination. FrankenPHP runs plain HTTP on the internal Docker network (its automatic HTTPS is turned off) and receives requests from nginx or Varnish. The path is: client → OpenResty (TLS, HTTP/2 or HTTP/3) → Varnish (when enabled) → FrankenPHP (plain HTTP).
Switching between PHP-FPM and FrankenPHP
PHP-FPM is the default and the traditional process model. Some third-party extensions are not compatible with persistent workers, so test on a stage environment before switching production to FrankenPHP.
To switch a running environment, open its Containers tab and use the PHP Runtime utility: choose PHP-FPM or FrankenPHP and apply. The platform re-installs the PHP container with the new runtime and keeps the PHP version number.
The runtime is stored as the suffix of CONTAINERS__PHP_VERSION in the environment file (.env). You can also change the suffix there yourself and then choose ⋯ → Install on the PHP container. Memory limits and OPcache settings apply the same way to both runtimes.
Security defaults
Several PHP security settings are hardened by default:
| Setting | Value | Reason |
|---|---|---|
display_errors | Off | No stack traces in HTTP responses |
expose_php | Off | No X-Powered-By: PHP header |
security.limit_extensions | .php | Only .php files executed via FPM socket |
rlimit_core | 0 | No core dumps |
Error output goes to stderr (forwarded to docker logs php) rather than to the HTTP response.