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.

Hub Containers tab showing the php container with FrankenPHP running

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 releasePHP version
2.4.98.4
2.4.88.4
2.4.78.3
2.4.68.2
Mage-OS 3.x8.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:

SettingDefaultOverride key
memory_limit2048 MBPHP__PHP_MEMORY_LIMIT
opcache.memory_consumption2048 MBPHP__PHP_OPCACHE_MEMORY
opcache.max_accelerated_files200,000—
opcache.validate_timestamps1 (checked every 4 s)—
max_execution_time7200 s—
upload_max_filesize / post_max_size256 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:

SettingValueReason
display_errorsOffNo stack traces in HTTP responses
expose_phpOffNo X-Powered-By: PHP header
security.limit_extensions.phpOnly .php files executed via FPM socket
rlimit_core0No core dumps

Error output goes to stderr (forwarded to docker logs php) rather than to the HTTP response.

On this page