From 26d07be89915f16b19f1662b5df49a4b8d37fed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 09:57:54 +0200 Subject: [PATCH 01/12] docs: add SEO frontmatter, llms.txt, and code-block hygiene Add YAML frontmatter (title, description) to every English doc under docs/*.md, rename generic headings (## Setup, ## Configuration, etc.) to topic-specific ones, fix code-block language tags where wrong, and add filename or purpose comments on blocks that represent a real file. Add a root llms.txt index pointing AI agents at the canonical English docs (Core Concepts, Setup and Build, Worker Mode and Extensions, Frameworks, Real-Time and Performance, Production and Observability). Translations under docs// are intentionally left untouched and should be regenerated by running docs/translate.php. --- docs/classic.md | 5 ++++ docs/compile.md | 7 ++++- docs/config.md | 23 ++++++++++++++-- docs/docker.md | 20 +++++++++++--- docs/early-hints.md | 7 ++++- docs/embed.md | 18 ++++++++----- docs/extension-workers.md | 27 ++++++++++++------- docs/extensions.md | 23 ++++++++++++++++ docs/github-actions.md | 5 ++++ docs/hot-reload.md | 16 +++++++---- docs/internals.md | 26 +++++++++++------- docs/known-issues.md | 6 +++++ docs/laravel.md | 14 +++++++--- docs/logging.md | 5 ++++ docs/mercure.md | 5 ++++ docs/metrics.md | 5 ++++ docs/migrate.md | 15 ++++++++++- docs/observability.md | 5 ++++ docs/performance.md | 29 ++++++++++++-------- docs/production.md | 14 +++++++--- docs/static.md | 13 ++++++--- docs/symfony.md | 18 ++++++++++--- docs/wordpress.md | 12 +++++++-- docs/worker.md | 33 +++++++++++++---------- docs/x-sendfile.md | 7 ++++- llms.txt | 56 +++++++++++++++++++++++++++++++++++++++ 26 files changed, 333 insertions(+), 81 deletions(-) create mode 100644 llms.txt diff --git a/docs/classic.md b/docs/classic.md index dd767a8fcc..ffc47db528 100644 --- a/docs/classic.md +++ b/docs/classic.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Classic Mode: Drop-in PHP-FPM Replacement +description: Run FrankenPHP in classic mode as a drop-in replacement for PHP-FPM or Apache mod_php, with a fixed or autoscaling thread pool serving PHP files directly. +--- + # Using Classic Mode Without any additional configuration, FrankenPHP operates in classic mode. In this mode, FrankenPHP functions like a traditional PHP server, directly serving PHP files. This makes it a seamless drop-in replacement for PHP-FPM or Apache with mod_php. diff --git a/docs/compile.md b/docs/compile.md index caae700126..aedf3d3290 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -1,3 +1,8 @@ +--- +title: Compile FrankenPHP From Sources With PHP as a Library +description: Build FrankenPHP from source on Linux, macOS and FreeBSD, link PHP as a dynamic library via xcaddy or go build, and add custom Caddy modules and extensions. +--- + # Compile From Sources This document explains how to create a FrankenPHP binary that will load PHP as a dynamic library. @@ -36,7 +41,7 @@ cd php-*/ Then, run the `configure` script with the options needed for your platform. The following `./configure` flags are mandatory, but you can add others, for example, to compile extensions or additional features. -#### Linux +#### Linux and FreeBSD ```console ./configure \ diff --git a/docs/config.md b/docs/config.md index e16d1fa185..44d488d8d2 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,3 +1,8 @@ +--- +title: Configuring FrankenPHP With Caddyfile, php.ini, and Env Vars +description: Configure FrankenPHP and Caddy via Caddyfile, JSON, or environment variables, including PHP runtime tuning, worker mode, file watching, and module options. +--- + # Configuration FrankenPHP, Caddy as well as the [Mercure](mercure.md) and [Vulcain](https://vulcain.rocks) modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config). @@ -9,6 +14,7 @@ You can specify a custom path with the `-c` or `--config` option. A minimal `Caddyfile` to serve a PHP application is shown below: ```caddyfile +# Caddyfile # The hostname to respond to localhost @@ -39,6 +45,7 @@ PHP: - You should copy an official template provided by the PHP project: ```dockerfile +# Dockerfile FROM dunglas/frankenphp # Production: @@ -80,6 +87,7 @@ The `php_server` or the `php` [HTTP directives](https://caddyserver.com/docs/cad Minimal example: ```caddyfile +# Caddyfile localhost { # Enable compression (optional) encode zstd br gzip @@ -91,6 +99,7 @@ localhost { You can also explicitly configure FrankenPHP using the [global option](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp`: ```caddyfile +# Caddyfile { frankenphp { num_threads # Sets the number of PHP threads to start. Default: 2x the number of available CPUs. @@ -116,6 +125,7 @@ You can also explicitly configure FrankenPHP using the [global option](https://c Alternatively, you may use the one-line short form of the `worker` option: ```caddyfile +# Caddyfile { frankenphp { worker @@ -128,6 +138,7 @@ Alternatively, you may use the one-line short form of the `worker` option: You can also define multiple workers if you serve multiple apps on the same server: ```caddyfile +# Caddyfile app.example.com { root /path/to/app/public php_server { @@ -155,6 +166,7 @@ it's a PHP file or not. Read more about it in the [performance page](performance Using the `php_server` directive is equivalent to this configuration: ```caddyfile +# Caddyfile route { # Add trailing slash for directory requests @canonicalPath { @@ -178,6 +190,7 @@ route { The `php_server` and the `php` directives have the following options: ```caddyfile +# Caddyfile php_server [] { root # Sets the root folder to the site. Default: `root` directive. split_path # Sets the substrings for splitting the URI into two parts. The first matching substring will be used to split the "path info" from the path. The first piece is suffixed with the matching substring and will be assumed as the actual resource (CGI script) name. The second piece will be set to PATH_INFO for the script to use. Default: `.php` @@ -205,6 +218,7 @@ Workers can instead be restarted on file changes via the `watch` directive. This is useful for development environments. ```caddyfile +# Caddyfile { frankenphp { worker { @@ -223,6 +237,7 @@ where the FrankenPHP process was started. You can instead also specify one or mo [shell filename pattern](https://pkg.go.dev/path/filepath#Match): ```caddyfile +# Caddyfile { frankenphp { worker { @@ -254,6 +269,7 @@ The following example will always serve a file in the public directory if presen and otherwise forward the request to the worker matching the path pattern. ```caddyfile +# Caddyfile { frankenphp { php_server { @@ -278,6 +294,7 @@ But when the fix depends on a third party you don't control, `max_requests` provides a pragmatic and hopefully temporary workaround for production: ```caddyfile +# Caddyfile { frankenphp { max_requests 500 @@ -285,7 +302,7 @@ But when the fix depends on a third party you don't control, } ``` -## Environment Variables +## FrankenPHP Environment Variables The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it: @@ -298,7 +315,7 @@ As for FPM and CLI SAPIs, environment variables are exposed by default in the `$ The `S` value of [the `variables_order` PHP directive](https://www.php.net/manual/en/ini.core.php#ini.variables-order) is always equivalent to `ES` regardless of the placement of `E` elsewhere in this directive. -## PHP config +## PHP Configuration in FrankenPHP To load [additional PHP configuration files](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan), the `PHP_INI_SCAN_DIR` environment variable can be used. @@ -307,6 +324,7 @@ When set, PHP will load all the file with the `.ini` extension present in the gi You can also change the PHP configuration using the `php_ini` directive in the `Caddyfile`: ```caddyfile +# Caddyfile { frankenphp { php_ini memory_limit 256M @@ -338,6 +356,7 @@ has been read. (for example: [Mercure](mercure.md), WebSocket, Server-Sent Event This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`: ```caddyfile +# Caddyfile { servers { enable_full_duplex diff --git a/docs/docker.md b/docs/docker.md index 3dcf65beb7..fb63ee758e 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Docker Image: Build, Configure, Extend +description: Build custom FrankenPHP Docker images, install PHP extensions and Caddy modules, run as non-root, harden with distroless, and enable worker mode by default. +--- + # Building Custom Docker Image [FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). @@ -13,11 +18,12 @@ The tags follow this pattern: `dunglas/frankenphp:-php-` in the `dist/` directory. -## Using The Binary +## Using the Embedded FrankenPHP Binary This is it! The `my-app` file (or `dist/frankenphp--` on other OSes) contains your self-contained app! @@ -125,18 +131,18 @@ You can also run the PHP CLI scripts embedded in your binary: ./my-app php-cli bin/console ``` -## PHP Extensions +## PHP Extensions in the Embedded Binary By default, the script will build extensions required by the `composer.json` file of your project, if any. If the `composer.json` file doesn't exist, the default extensions are built, as documented in [the static builds entry](static.md). To customize the extensions, use the `PHP_EXTENSIONS` environment variable. -## Customizing The Build +## Customizing the Embedded Binary Build [Read the static build documentation](static.md) to see how to customize the binary (extensions, PHP version...). -## Distributing The Binary +## Distributing the Embedded Binary On Linux, the created binary is compressed using [UPX](https://upx.github.io). diff --git a/docs/extension-workers.md b/docs/extension-workers.md index dd8527d272..5061aa6696 100644 --- a/docs/extension-workers.md +++ b/docs/extension-workers.md @@ -1,14 +1,20 @@ +--- +title: FrankenPHP Extension Workers: Background PHP Thread Pools +description: Use FrankenPHP Extension Workers to run a dedicated PHP thread pool from a Go extension for queues, schedulers, event listeners, and custom protocols. +--- + # Extension Workers Extension Workers enable your [FrankenPHP extension](https://frankenphp.dev/docs/extensions/) to manage a dedicated pool of PHP threads for executing background tasks, handling asynchronous events, or implementing custom protocols. Useful for queue systems, event listeners, schedulers, etc. -## Registering the Worker +## Registering a FrankenPHP Extension Worker -### Static Registration +### Static Worker Registration If you don't need to make the worker configurable by the user (fixed script path, fixed number of threads), you can simply register the worker in the `init()` function. ```go +// FrankenPHP extension worker static registration package myextension import ( @@ -35,13 +41,13 @@ func init() { ### In a Caddy Module (Configurable by the user) -If you plan to share your extension (like a generic queue or event listener), you should wrap it in a Caddy module. This allows users to configure the script path and thread count via their `Caddyfile`. This requires implementing the `caddy.Provisioner` interface and parsing the Caddyfile ([see an example](https://github.com/dunglas/frankenphp-queue/blob/989120d394d66dd6c8e2101cac73dd622fade334/caddy.go)). +If you plan to share your extension (like a generic queue or event listener), you should wrap it in a Caddy module. This allows users to configure the script path and thread count via their `Caddyfile`. This requires implementing the `caddy.Provisioner` interface and parsing the Caddyfile ([see the frankenphp-queue Caddy module example](https://github.com/dunglas/frankenphp-queue/blob/main/caddy.go)). ### In a Pure Go Application (Embedding) If you are [embedding FrankenPHP in a standard Go application without caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), you can register extension workers using `frankenphp.WithExtensionWorkers` when initializing options. -## Interacting with Workers +## Dispatching Tasks to FrankenPHP Extension Workers Once the worker pool is active, you can dispatch tasks to it. This can be done inside [native functions exported to PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), or from any Go logic such as a cron scheduler, an event listener (MQTT, Kafka), or a any other goroutine. @@ -49,9 +55,10 @@ Once the worker pool is active, you can dispatch tasks to it. This can be done i Use `SendMessage` to pass raw data directly to your worker script. This is ideal for queues or simple commands. -#### Example: An Async Queue Extension +#### Async Queue Extension Example ```go +// FrankenPHP extension: dispatch raw messages to a worker via SendMessage // #include import "C" import ( @@ -83,6 +90,7 @@ func my_queue_push(data *C.zval) bool { Use `SendRequest` if your extension needs to invoke a PHP script that expects a standard web environment (populating `$_SERVER`, `$_GET`, etc.). ```go +// FrankenPHP extension: invoke a worker PHP script via SendRequest (HTTP emulation) // #include import "C" import ( @@ -109,13 +117,13 @@ func my_worker_http_request(path *C.zend_string) unsafe.Pointer { } ``` -## Worker Script +## FrankenPHP Extension Worker PHP Script The PHP worker script runs in a loop and can handle both raw messages and HTTP requests. ```php @@ -180,6 +186,7 @@ If order or association are not needed, it's also possible to directly convert t **Creating and manipulating arrays in Go:** ```go +// Converting between PHP arrays and Go maps/slices package example // #include @@ -279,6 +286,7 @@ FrankenPHP provides a way to work with PHP callables using the `frankenphp.CallP To showcase this, let's create our own `array_map()` function that takes a callable and an array, applies the callable to each element of the array, and returns a new array with the results: ```go +// Calling a PHP callable from a Go-defined extension function // export_php:function my_array_map(array $data, callable $callback): array func my_array_map(arr *C.zend_array, callback *C.zval) unsafe.Pointer { goSlice, err := frankenphp.GoPackedArray[any](unsafe.Pointer(arr)) @@ -313,6 +321,7 @@ $result = my_array_map(['hello', 'world'], 'strtoupper'); The generator supports declaring **opaque classes** as Go structs, which can be used to create PHP objects. You can use the `//export_php:class` directive comment to define a PHP class. For example: ```go +// Declaring a PHP class backed by a Go struct package example //export_php:class User @@ -339,6 +348,7 @@ This approach provides better encapsulation and prevents PHP code from accidenta Since properties are not directly accessible, you **must define methods** to interact with your opaque classes. Use the `//export_php:method` directive to define behavior: ```go +// Defining methods on a Go-backed PHP class package example // #include @@ -381,6 +391,7 @@ func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) { The generator supports nullable parameters using the `?` prefix in PHP signatures. When a parameter is nullable, it becomes a pointer in your Go function, allowing you to check if the value was `null` in PHP: ```go +// Handling nullable PHP parameters in a Go method package example // #include @@ -455,6 +466,7 @@ The generator supports exporting Go constants to PHP using two directives: `//ex Use the `//export_php:const` directive to create global PHP constants: ```go +// Exporting global PHP constants from Go package example //export_php:const @@ -479,6 +491,7 @@ const ( Use the `//export_php:classconst ClassName` directive to create constants that belong to a specific PHP class: ```go +// Exporting PHP class constants from Go package example //export_php:classconst User @@ -522,6 +535,7 @@ The directive supports various value types, including strings, integers, boolean You can use constants just like you are used to in the Go code. For example, let's take the `repeat_this()` function we declared earlier and change the last argument to an integer: ```go +// Combining functions, classes, methods, and constants in one extension package example // #include @@ -590,6 +604,7 @@ The generator supports organizing your PHP extension's functions, classes, and c Use the `//export_php:namespace` directive at the top of your Go file to place all exported symbols under a specific namespace: ```go +// Placing exported symbols under a PHP namespace //export_php:namespace My\Extension package example @@ -653,6 +668,7 @@ We'll see how to write a simple PHP extension in Go that defines a new native fu In your module, you need to define a new native function that will be called from PHP. To do this, create a file with the name you want, for example, `extension.go`, and add the following code: ```go +// extension.go package example // #include "extension.h" @@ -686,6 +702,7 @@ To allow PHP to call our function, we need to define a corresponding PHP functio ```php #include "extension.h" #include "extension_arginfo.h" @@ -774,6 +793,7 @@ To define the new PHP function, we will modify our `extension.stub.php` file to ```php diff --git a/docs/github-actions.md b/docs/github-actions.md index f9f9afab80..95bc5477bd 100644 --- a/docs/github-actions.md +++ b/docs/github-actions.md @@ -1,3 +1,8 @@ +--- +title: Building FrankenPHP Docker Images with GitHub Actions +description: Use GitHub Actions to automatically build and publish FrankenPHP Docker images to a registry on pull requests, merges to main, and tagged releases. +--- + # Using GitHub Actions This repository builds and deploys the Docker image to [Docker Hub](https://hub.docker.com/r/dunglas/frankenphp) on diff --git a/docs/hot-reload.md b/docs/hot-reload.md index 49790d1e68..65ca9b4004 100644 --- a/docs/hot-reload.md +++ b/docs/hot-reload.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Hot Reload for PHP, Templates, and Assets +description: Use FrankenPHP hot reload to update PHP code, templates, and frontend assets in the browser without manual refresh, with optional DOM morphing via Idiomorph. +--- + # Hot Reload FrankenPHP includes a built-in **hot reload** feature designed to vastly improve the developer experience. @@ -18,7 +23,7 @@ Depending on your setup, the browser will either: - **Morph the DOM** (preserving scroll position and input state) if [Idiomorph](https://github.com/bigskysoftware/idiomorph) is loaded. - **Reload the page** (standard live reload) if Idiomorph is not present. -## Configuration +## Enabling FrankenPHP Hot Reload To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`. @@ -78,7 +83,7 @@ php_server { } ``` -## Client-Side Integration +## Client-Side Integration for FrankenPHP Hot Reload While the server detects changes, the browser needs to subscribe to these events to update the page. FrankenPHP exposes the Mercure Hub URL to use for subscribing to file changes via the `$_SERVER['FRANKENPHP_HOT_RELOAD']` environment variable. @@ -111,7 +116,7 @@ To do so, add the `data-frankenphp-hot-reload-preserve` attribute to the relevan
``` -## Worker Mode +## Hot Reload with FrankenPHP Worker Mode If you are running your application in [Worker Mode](https://frankenphp.dev/docs/worker/), your application script remains in memory. This means changes to your PHP code will not be reflected immediately, even if the browser reloads. @@ -121,7 +126,8 @@ For the best developer experience, you should combine `hot_reload` with [the `wa - `hot_reload`: refreshes the **browser** when files change - `worker.watch`: restarts the worker when files change -```caddy +```caddyfile +# Caddyfile: combine FrankenPHP hot reload with worker watch for full dev workflow localhost mercure { @@ -138,7 +144,7 @@ php_server { } ``` -## How It Works +## How FrankenPHP Hot Reload Works 1. **Watch**: FrankenPHP monitors the filesystem for modifications using [the `e-dant/watcher` library](https://github.com/e-dant/watcher) under the hood (we contributed the Go binding). 2. **Restart (Worker Mode)**: if `watch` is enabled in the worker config, the PHP worker is restarted to load the new code. diff --git a/docs/internals.md b/docs/internals.md index bd1bb636fa..3d60a06bcf 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -1,8 +1,13 @@ +--- +title: FrankenPHP Internals: Threads, State Machine, and CGO +description: How FrankenPHP works inside: PHP ZTS thread pool, Go state machine, CGO boundary with the PHP SAPI, auto-scaling, and per-thread environment sandboxing. +--- + # Internals This document explains FrankenPHP's internal architecture, focusing on thread management, the state machine, and the CGO boundary between Go and C/PHP. -## Overview +## FrankenPHP Architecture Overview FrankenPHP embeds the PHP interpreter directly into Go via CGO. Each PHP execution runs on a real POSIX thread (not a goroutine) because PHP's ZTS (Zend Thread Safety) model requires it. Go orchestrates these threads through a state machine, while C handles the PHP SAPI lifecycle. @@ -12,7 +17,7 @@ The main layers are: 2. **C layer** (`frankenphp.c`, `frankenphp.h`): PHP SAPI implementation, script execution loop, superglobal management 3. **State machine** (`internal/state/`): Synchronization between Go goroutines and C threads -## Thread Types +## FrankenPHP Thread Types ### Main Thread (`phpmainthread.go`) @@ -48,11 +53,11 @@ Keep a PHP script alive across multiple requests. The PHP script calls `frankenp After the script exits, the worker is restarted immediately if it had reached `frankenphp_handle_request()` at least once (whether the exit was clean or the result of a fatal error). Exponential backoff is only applied to consecutive startup failures, where the script exits before ever reaching `frankenphp_handle_request()`. -## Thread State Machine +## FrankenPHP Thread State Machine Each thread has a `ThreadState` (defined in `internal/state/state.go`) that governs its lifecycle. The state machine uses a `sync.RWMutex` for all state transitions and a channel-based subscriber pattern for blocking waits. -### States +### FrankenPHP Thread States ```text Lifecycle: Reserved → BootRequested → Booting → Inactive → Ready ⇄ (processing) @@ -85,7 +90,7 @@ The full set of states is defined in `internal/state/state.go`: | `TransitionInProgress` | The C thread has acknowledged the transition request. | | `TransitionComplete` | The Go side has installed the new handler. | -### Key Operations +### Key State Machine Operations **`RequestSafeStateChange(nextState)`**: The primary way external goroutines request state changes. It: @@ -148,7 +153,7 @@ Set(Ready) state is Ready → normal execution ``` -## CGO Boundary +## CGO Boundary Between Go and PHP ### Exported Go Functions @@ -174,6 +179,7 @@ All these functions receive a `threadIndex` parameter identifying the calling th Each PHP thread runs `php_thread()` in `frankenphp.c`: ```c +// frankenphp.c: php_thread() main script execution loop while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { php_request_startup(); php_execute_script(&file_handle); @@ -184,17 +190,17 @@ while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { Bailouts (fatal PHP errors) are caught by `zend_catch`, which marks the thread as unhealthy and forces cleanup. -### Memory Management +### Memory Management Across the CGO Boundary - **Go → C strings**: `C.CString()` allocates with `malloc()`. The C side is responsible for freeing (e.g., `frankenphp_free_request_context()` frees cookie data). - **Go string pinning**: `phpThread` (in `phpthread.go`) embeds Go's [`runtime.Pinner`](https://pkg.go.dev/runtime#Pinner). `thread.Pin()` / `thread.Unpin()` keep Go memory referenced from C alive without copying it. The thread is unpinned after each script execution. - **PHP memory**: Managed by Zend's memory manager (`emalloc`/`efree`). Automatically freed at request shutdown. -## Auto-Scaling +## FrankenPHP Thread Auto-Scaling FrankenPHP can automatically scale the number of PHP threads based on demand (`scaling.go`). -### Configuration +### Auto-Scaling Configuration - `num_threads`: Initial number of threads started at boot - `max_threads`: Maximum number of threads allowed (includes auto-scaled) @@ -215,7 +221,7 @@ A dedicated goroutine reads from an unbuffered `scaleChan`: A separate goroutine periodically checks (every 5s) for idle auto-scaled threads. Threads in `Ready` state idle longer than `maxIdleTime` (default 5s) are converted to `Inactive` (up to 10 per cycle). They are not fully stopped: a code path exists for that, but it is currently disabled because some PECL extensions leak memory and prevent threads from cleanly shutting down. -## Environment Sandboxing +## Per-Thread Environment Sandboxing FrankenPHP sandboxes environment variables per-thread: diff --git a/docs/known-issues.md b/docs/known-issues.md index da22690373..207944f3c3 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Known Issues, Incompatible Extensions, and Workarounds +description: Reference of FrankenPHP limitations, including unsupported PHP extensions, musl libc caveats, Docker TLS setup with 127.0.0.1, and Composer @php scripts. +--- + # Known Issues ## Unsupported PHP Extensions @@ -91,6 +96,7 @@ As a workaround, we can create a shell script in `/usr/local/bin/php` which stri ```bash #!/usr/bin/env bash +# /usr/local/bin/php args=("$@") index=0 for i in "$@" diff --git a/docs/laravel.md b/docs/laravel.md index 182557c9db..08cfcc9bcc 100644 --- a/docs/laravel.md +++ b/docs/laravel.md @@ -1,6 +1,11 @@ +--- +title: Running Laravel with FrankenPHP (Docker, Octane, Standalone Binary) +description: How to run a Laravel application with FrankenPHP using the Docker image, a local install, Laravel Octane, or as an embedded standalone binary. +--- + # Laravel -## Docker +## Running Laravel with the FrankenPHP Docker Image Serving a [Laravel](https://laravel.com) web application with FrankenPHP is as easy as mounting the project in the `/app` directory of the official Docker image. @@ -12,7 +17,7 @@ docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp And enjoy! -## Local Installation +## Installing Laravel with FrankenPHP Locally Alternatively, you can run your Laravel projects with FrankenPHP from your local machine: @@ -20,6 +25,7 @@ Alternatively, you can run your Laravel projects with FrankenPHP from your local 2. Add the following configuration to a file named `Caddyfile` in the root directory of your Laravel project: ```caddyfile + # Caddyfile { frankenphp } @@ -90,6 +96,7 @@ Follow these steps to package your Laravel app as a standalone binary for Linux: 1. Create a file named `static-build.Dockerfile` in the repository of your app: ```dockerfile + # static-build.Dockerfile FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu # If you intend to run the binary on musl-libc systems, use static-builder-musl instead @@ -179,6 +186,7 @@ If you are not using [Octane](#laravel-octane), see [the Mercure documentation e If you are using Octane, you can use enable Mercure support by adding the following lines to your `config/octane.php` file: ```php +// config/octane.php // ... return [ @@ -201,7 +209,7 @@ Alternatively, see [the Mercure documentation](mercure.md) to do it in pure PHP It's even possible to package Laravel Octane apps as standalone binaries! -To do so, [install Octane properly](#laravel-octane) and follow the steps described in [the previous section](#laravel-apps-as-standalone-binaries). +To do so, [install Octane properly](#laravel-octane) and follow the steps described in [the section on Laravel apps as standalone binaries](#laravel-apps-as-standalone-binaries). Then, to start FrankenPHP in worker mode through Octane, run: diff --git a/docs/logging.md b/docs/logging.md index 7f68b3dc00..77feb18a84 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Logging with frankenphp_log() and Caddy +description: Emit structured logs from PHP with frankenphp_log() or error_log() in FrankenPHP, routed through Caddy's logging system as JSON for Datadog, Loki, or Elastic. +--- + # Logging > [!TIP] diff --git a/docs/mercure.md b/docs/mercure.md index 25a619b878..abec2994a0 100644 --- a/docs/mercure.md +++ b/docs/mercure.md @@ -1,3 +1,8 @@ +--- +title: Real-Time Updates with the FrankenPHP Mercure Hub +description: FrankenPHP ships with a built-in Mercure hub for pushing real-time events to browsers over HTTP, as a simpler alternative to WebSockets. +--- + # Real-time FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub! diff --git a/docs/metrics.md b/docs/metrics.md index 83bae898ba..2fa6860196 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Prometheus Metrics for Threads and Workers +description: List of Prometheus-compatible metrics exposed by FrankenPHP via Caddy, covering PHP threads, workers, request times, queue depth, crashes, and restarts. +--- + # Metrics > [!TIP] diff --git a/docs/migrate.md b/docs/migrate.md index 07016fef10..139cab578d 100644 --- a/docs/migrate.md +++ b/docs/migrate.md @@ -1,3 +1,8 @@ +--- +title: Migrating from Nginx and PHP-FPM to FrankenPHP +description: Step-by-step guide to migrate a PHP application from an Nginx plus PHP-FPM stack to FrankenPHP, covering Caddyfile, php.ini, threads, and Docker. +--- + # Migrating from Nginx/PHP-FPM FrankenPHP replaces both your web server (Nginx, Apache) and PHP-FPM with a single binary. @@ -19,6 +24,7 @@ This guide covers a basic migration for a typical PHP application. A typical Nginx + PHP-FPM configuration: ```nginx +# /etc/nginx/sites-available/example.com server { listen 80; server_name example.com; @@ -40,6 +46,7 @@ server { Becomes a single `Caddyfile`: ```caddyfile +# Caddyfile example.com { root /var/www/app/public php_server @@ -55,6 +62,7 @@ Your existing `php.ini` works as-is. See [Configuration](config.md) for where to You can also set directives directly in the `Caddyfile`: ```caddyfile +# Caddyfile { frankenphp { php_ini memory_limit 256M @@ -74,6 +82,7 @@ In PHP-FPM, you tune `pm.max_children` to control the number of worker processes In FrankenPHP, the equivalent is `num_threads`: ```caddyfile +# Caddyfile { frankenphp { num_threads 16 @@ -84,6 +93,7 @@ In FrankenPHP, the equivalent is `num_threads`: By default, FrankenPHP starts 2 threads per CPU. For dynamic scaling similar to PHP-FPM's `pm = dynamic`: ```caddyfile +# Caddyfile { frankenphp { num_threads 4 @@ -99,6 +109,7 @@ A typical PHP-FPM Docker setup using two containers (Nginx + PHP-FPM) can be rep **Before:** ```yaml +# compose.yaml services: nginx: image: nginx:1 @@ -119,6 +130,7 @@ services: **After:** ```yaml +# compose.yaml services: php: image: dunglas/frankenphp:1-php8.5 @@ -140,7 +152,7 @@ volumes: If you need additional PHP extensions, see [Building Custom Docker Image](docker.md#how-to-install-more-php-extensions). -For framework-specific Docker setups, see [Symfony Docker](https://github.com/dunglas/symfony-docker) and [Laravel](laravel.md#docker). +For framework-specific Docker setups, see [Symfony Docker](https://github.com/dunglas/symfony-docker) and [running Laravel with the FrankenPHP Docker image](laravel.md#running-laravel-with-the-frankenphp-docker-image). ## Step 5: Consider Worker Mode (Optional) @@ -149,6 +161,7 @@ In [classic mode](classic.md), FrankenPHP works like PHP-FPM: each request boots For better performance, you can switch to [worker mode](worker.md), which boots your application once and keeps it in memory: ```caddyfile +# Caddyfile example.com { root /var/www/app/public php_server { diff --git a/docs/observability.md b/docs/observability.md index 0e58c07e9a..750b717008 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Observability with Metrics, Logs, and Ember TUI +description: Monitor FrankenPHP in development and production using Prometheus metrics, structured logs, the Ember TUI dashboard, and custom Grafana scraping setups. +--- + # Observability FrankenPHP provides built-in observability features: [Prometheus-compatible metrics](metrics.md) and [structured logging](logging.md). diff --git a/docs/performance.md b/docs/performance.md index 5ca732d7f7..e1b5f9540c 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -1,9 +1,14 @@ +--- +title: FrankenPHP Performance Tuning Guide +description: Tune FrankenPHP for higher throughput and lower latency: thread count, worker mode, glibc vs musl, Go runtime, OPcache, and Caddyfile options. +--- + # Performance By default, FrankenPHP tries to offer a good compromise between performance and ease of use. However, it is possible to substantially improve performance using an appropriate configuration. -## Number of Threads and Workers +## Tuning FrankenPHP Threads and Workers By default, FrankenPHP starts 2 times more threads and workers (in worker mode) than the available number of CPU cores. @@ -26,13 +31,13 @@ If set to `auto`, the limit will be estimated based on the `memory_limit` in you `max_threads` is similar to PHP FPM's [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children). The main difference is that FrankenPHP uses threads instead of processes and automatically delegates them across different worker scripts and 'classic mode' as needed. -## Worker Mode +## Worker Mode for Higher Throughput -Enabling [the worker mode](worker.md) dramatically improves performance, +Enabling [the FrankenPHP worker mode](worker.md) dramatically improves performance, but your app must be adapted to be compatible with this mode: you need to create a worker script and to be sure that the app is not leaking memory. -## Don't Use musl +## Avoid musl in Production: Prefer glibc Builds The Alpine Linux variant of the official Docker images and the default binaries we provide are using [the musl libc](https://musl.libc.org). @@ -47,7 +52,7 @@ This can be achieved by using the Debian Docker images, using [our maintainers . For leaner or more secure containers, you may want to consider [a hardened Debian image](docker.md#hardening-images) rather than Alpine. -## Go Runtime Configuration +## Go Runtime Configuration for FrankenPHP FrankenPHP is written in Go. @@ -59,7 +64,7 @@ You likely want to set the `GODEBUG` environment variable to `cgocheck=0` (the d If you run FrankenPHP in containers (Docker, Kubernetes, LXC...) and limit the memory available for the containers, set the `GOMEMLIMIT` environment variable to the available amount of memory. -For more details, [the Go documentation page dedicated to this subject](https://pkg.go.dev/runtime#hdr-Environment_Variables) is a must-read to get the most out of the runtime. +For more details, [read the Go runtime environment variables reference](https://pkg.go.dev/runtime#hdr-Environment_Variables) to get the most out of the runtime. ## `file_server` @@ -107,6 +112,7 @@ files from PHP by path. This approach works well if your entire application is s An example [configuration](config.md#caddyfile-config) that serves static files behind an `/assets` folder could look like this: ```caddyfile +# Caddyfile: split static assets and PHP requests to skip filesystem lookups route { @assets { path /assets/* @@ -125,7 +131,7 @@ route { } ``` -## Placeholders +## Avoid Caddyfile Placeholders in Hot Paths You can use [placeholders](https://caddyserver.com/docs/conventions#placeholders) in the `root` and `env` directives. However, this prevents caching these values, and comes with a significant performance cost. @@ -146,14 +152,14 @@ php_server { This will improve performance if the `root` directive contains [placeholders](https://caddyserver.com/docs/conventions#placeholders). The gain will be negligible in other cases. -## Logs +## FrankenPHP Logging Performance Logging is obviously very useful, but, by definition, it requires I/O operations and memory allocations, which considerably reduces performance. Make sure you [set the logging level](https://caddyserver.com/docs/caddyfile/options#log) correctly, and only log what's necessary. -## PHP Performance +## PHP Performance Tuning for FrankenPHP FrankenPHP uses the official PHP interpreter. All usual PHP-related performance optimizations apply with FrankenPHP. @@ -165,10 +171,10 @@ In particular: - ensure that the `realpath` cache is big enough for the needs of your application - use [preloading](https://www.php.net/manual/en/opcache.preloading.php) -For more details, read [the dedicated Symfony documentation entry](https://symfony.com/doc/current/performance.html) +For more details, read [the Symfony performance tuning documentation](https://symfony.com/doc/current/performance.html) (most tips are useful even if you don't use Symfony). -## Splitting The Thread Pool +## Splitting the FrankenPHP Thread Pool for Slow Endpoints It is common for applications to interact with slow external services, like an API that tends to be unreliable under high load or consistently takes 10+ seconds to respond. @@ -178,6 +184,7 @@ limits the concurrency of requests going towards the slow endpoint, similar to a connection pool. ```caddyfile +# Caddyfile: dedicated FrankenPHP thread pool for slow endpoints example.com { php_server { root /app/public # the root of your application diff --git a/docs/production.md b/docs/production.md index 7b6898d6f5..e10d33b9df 100644 --- a/docs/production.md +++ b/docs/production.md @@ -1,3 +1,8 @@ +--- +title: Deploying FrankenPHP in Production with Docker Compose +description: Deploy a PHP application to production with FrankenPHP and Docker Compose on a single Linux server, including TLS, reverse proxy, and multi-node setups. +--- + # Deploying in Production In this tutorial, we will learn how to deploy a PHP application on a single server using Docker Compose. @@ -11,6 +16,7 @@ If you're using API Platform (which also uses FrankenPHP), refer to [the deploym First, create a `Dockerfile` in the root directory of your PHP project: ```dockerfile +# Dockerfile FROM dunglas/frankenphp # Be sure to replace "your-domain-name.example.com" by your domain name @@ -39,6 +45,7 @@ be sure to include it in the Docker image and to install your dependencies. Then, add a `compose.yaml` file: ```yaml +# compose.yaml services: php: image: dunglas/frankenphp @@ -72,9 +79,9 @@ Finally, if you use Git, commit these files and push. To deploy your application in production, you need a server. In this tutorial, we will use a virtual machine provided by DigitalOcean, but any Linux server can work. -If you already have a Linux server with Docker installed, you can skip straight to [the next section](#configuring-a-domain-name). +If you already have a Linux server with Docker installed, you can skip straight to [Configuring a Domain Name](#configuring-a-domain-name). -Otherwise, use [this affiliate link](https://m.do.co/c/5d8aabe3ab80) to get $200 of free credit, create an account, then click on "Create a Droplet". +Otherwise, use [this DigitalOcean affiliate link](https://m.do.co/c/5d8aabe3ab80) to get $200 of free credit, create an account, then click on "Create a Droplet". Then, click on the "Marketplace" tab under the "Choose an image" section and search for the app named "Docker". This will provision an Ubuntu server with the latest versions of Docker and Docker Compose already installed! @@ -112,7 +119,7 @@ Example with the DigitalOcean Domains service ("Networking" > "Domains"): > > Let's Encrypt, the service used by default by FrankenPHP to automatically generate a TLS certificate doesn't support using bare IP addresses. Using a domain name is mandatory to use Let's Encrypt. -## Deploying +## Deploying FrankenPHP with Docker Compose Copy your project on the server using `git clone`, `scp`, or any other tool that may fit your need. If you use GitHub, you may want to use [a deploy key](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys). @@ -144,6 +151,7 @@ you must configure the [`trusted_proxies` global option](https://caddyserver.com so that Caddy trusts incoming `X-Forwarded-*` headers: ```caddyfile +# Caddyfile { servers { trusted_proxies static diff --git a/docs/static.md b/docs/static.md index 516acb3e91..06b7398586 100644 --- a/docs/static.md +++ b/docs/static.md @@ -1,3 +1,8 @@ +--- +title: FrankenPHP Static Build: Single-Binary PHP App Server +description: Create static or mostly static FrankenPHP binaries with embedded PHP and Caddy on Linux and macOS, customize extensions, and load dynamic modules with glibc. +--- + # Create a Static Build Instead of using a local installation of the PHP library, @@ -48,7 +53,7 @@ The resulting mostly static (except `glibc`) binary is named `frankenphp` and is If you want to build the static binary without Docker, take a look at the macOS instructions, which also work for Linux. -### Custom Extensions +### Custom PHP Extensions in the Static Build By default, the most popular PHP extensions are compiled. @@ -89,7 +94,7 @@ In this example, we add the [Souin](https://souin.io) HTTP cache module for Cadd > The cbrotli, Mercure, and Vulcain modules are included by default if `XCADDY_ARGS` is empty or not set. > If you customize the value of `XCADDY_ARGS`, you must include them explicitly if you want them to be included. -See also how to [customize the build](#customizing-the-build) +See also how to [customize the FrankenPHP static build](#customizing-the-frankenphp-static-build) ### GitHub Token @@ -112,7 +117,7 @@ cd frankenphp Note: this script also works on Linux (and probably on other Unixes), and is used internally by the Docker images we provide. -## Customizing The Build +## Customizing the FrankenPHP Static Build The following environment variables can be passed to `docker build` and to the `build-static.sh` script to customize the static build: @@ -129,7 +134,7 @@ script to customize the static build: - `MIMALLOC`: (experimental, Linux-only) replace musl's mallocng by [mimalloc](https://github.com/microsoft/mimalloc) for improved performance. We only recommend using this for musl targeting builds, for glibc prefer disabling this option and using [`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) when you run your binary instead. - `RELEASE`: (maintainers only) when set, the resulting binary will be uploaded on GitHub -## Extensions +## Loading PHP Extensions Dynamically in the Static Binary With the glibc or macOS-based binaries, you can load PHP extensions dynamically. However, these extensions will have to be compiled with ZTS support. Since most package managers do not currently offer ZTS versions of their extensions, you will have to compile them yourself. diff --git a/docs/symfony.md b/docs/symfony.md index 7c41b49df9..fba4d4f235 100644 --- a/docs/symfony.md +++ b/docs/symfony.md @@ -1,10 +1,15 @@ +--- +title: Running Symfony with FrankenPHP (Docker, Worker Mode, Hot Reload) +description: How to run a Symfony application with FrankenPHP using Symfony Docker, a local install, worker mode, hot reload, AssetMapper, and X-Sendfile. +--- + # Symfony -## Docker +## Running Symfony with the Symfony Docker Image For [Symfony](https://symfony.com) projects, we recommend using [Symfony Docker](https://github.com/dunglas/symfony-docker), the official Symfony Docker setup maintained by FrankenPHP's author. It provides a complete Docker-based environment with FrankenPHP, automatic HTTPS, HTTP/2, HTTP/3, and worker mode support out of the box. -## Local Installation +## Installing Symfony with FrankenPHP Locally Alternatively, you can run your Symfony projects with FrankenPHP from your local machine: @@ -12,6 +17,7 @@ Alternatively, you can run your Symfony projects with FrankenPHP from your local 2. Add the following configuration to a file named `Caddyfile` in the root directory of your Symfony project: ```caddyfile + # Caddyfile # The domain name of your server localhost @@ -26,7 +32,7 @@ Alternatively, you can run your Symfony projects with FrankenPHP from your local 3. Start FrankenPHP from the root directory of your Symfony project: `frankenphp run` -## Worker Mode +## Symfony Worker Mode with FrankenPHP Since Symfony 7.4, FrankenPHP worker mode is natively supported. @@ -58,13 +64,14 @@ composer require --dev igor-php/igor-php vendor/bin/igor-php . ``` -## Hot Reload +## Hot Reload for Symfony Hot reloading is enabled by default in [Symfony Docker](https://github.com/dunglas/symfony-docker). To use the [hot reload](hot-reload.md) feature without Symfony Docker, enable [Mercure](mercure.md) and add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`: ```caddyfile +# Caddyfile localhost mercure { @@ -81,6 +88,7 @@ php_server { Then, add the following code to your `templates/base.html.twig` file: ```twig +{# templates/base.html.twig #} {% if app.request.server.has('FRANKENPHP_HOT_RELOAD') %} @@ -103,6 +111,7 @@ Symfony's [AssetMapper component](https://symfony.com/doc/current/frontend/asset 2. Update your `Caddyfile` to serve pre-compressed assets: ```caddyfile + # Caddyfile localhost @assets path /assets/* @@ -167,6 +176,7 @@ Follow these steps to prepare and package your Symfony app: 2. Create a file named `static-build.Dockerfile` in the repository of your app: ```dockerfile + # static-build.Dockerfile FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-gnu # If you intend to run the binary on musl-libc systems, use static-builder-musl instead diff --git a/docs/wordpress.md b/docs/wordpress.md index bc9f2756fc..f65147a56d 100644 --- a/docs/wordpress.md +++ b/docs/wordpress.md @@ -1,8 +1,13 @@ +--- +title: Running WordPress with FrankenPHP (HTTPS, HTTP/3, Hot Reload) +description: How to run WordPress with FrankenPHP, including a minimal install, a production Caddyfile, and enabling hot reload via Mercure. +--- + # WordPress Run [WordPress](https://wordpress.org/) with FrankenPHP to enjoy a modern, high-performance stack with automatic HTTPS, HTTP/3, and Zstandard compression. -## Minimal Installation +## Installing WordPress with FrankenPHP 1. [Download WordPress](https://wordpress.org/download/) 2. Extract the ZIP archive and open a terminal in the extracted directory @@ -18,6 +23,7 @@ Run [WordPress](https://wordpress.org/) with FrankenPHP to enjoy a modern, high- For a production-ready setup, prefer using `frankenphp run` with a `Caddyfile` like this one: ```caddyfile +# Caddyfile example.com php_server @@ -25,11 +31,12 @@ encode zstd br gzip log ``` -## Hot Reload +## Hot Reload for WordPress To use the [hot reload](hot-reload.md) feature with WordPress, enable [Mercure](mercure.md) and add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`: ```caddyfile +# Caddyfile localhost mercure { @@ -44,6 +51,7 @@ php_server { Then, add the code needed to load the JavaScript libraries in the `functions.php` file of your WordPress theme: ```php +// wp-content/themes//functions.php function hot_reload() { ?> diff --git a/docs/worker.md b/docs/worker.md index 30cc0cfa73..67df8f6fc2 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -1,15 +1,20 @@ +--- +title: FrankenPHP Worker Mode: Keep Your PHP App in Memory +description: Run FrankenPHP in worker mode to keep your PHP application bootstrapped between requests, cut bootstrap overhead, and serve responses in milliseconds. +--- + # Using FrankenPHP Workers Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds. -## Starting Worker Scripts +## Starting FrankenPHP Worker Scripts -### Docker +### Running a FrankenPHP Worker with Docker Set the value of the `FRANKENPHP_CONFIG` environment variable to `worker /path/to/your/worker/script.php`: -```console +```bash docker run \ -e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \ -v $PWD:/app \ @@ -17,11 +22,11 @@ docker run \ dunglas/frankenphp ``` -### Standalone Binary +### Running a FrankenPHP Worker with the Standalone Binary Use the `--worker` option of the `php-server` command to serve the content of the current directory using a worker: -```console +```bash frankenphp php-server --worker /path/to/your/worker/script.php ``` @@ -31,21 +36,21 @@ It will be used automatically. It's also possible to [restart the worker on file changes](config.md#watching-for-file-changes) with the `--watch` option. The following command will trigger a restart if any file ending in `.php` in the `/path/to/your/app/` directory or subdirectories is modified: -```console +```bash frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php" ``` This feature is often used in combination with [hot reloading](hot-reload.md). -## Symfony +## Worker Mode for Symfony -See [the dedicated documentation](symfony.md#worker-mode). +See [the FrankenPHP Symfony worker mode documentation](symfony.md#worker-mode). -## Laravel Octane +## Worker Mode for Laravel Octane -See [the dedicated documentation](laravel.md#laravel-octane). +See [the FrankenPHP Laravel Octane documentation](laravel.md#laravel-octane). -## Custom Apps +## Writing a Custom FrankenPHP Worker Script The following example shows how to create your own worker script without relying on a third-party library: @@ -91,7 +96,7 @@ $myApp->shutdown(); Then, start your app and use the `FRANKENPHP_CONFIG` environment variable to configure your worker: -```console +```bash docker run \ -e FRANKENPHP_CONFIG="worker ./public/index.php" \ -v $PWD:/app \ @@ -102,7 +107,7 @@ docker run \ By default, 2 workers per CPU are started. You can also configure the number of workers to start: -```console +```bash docker run \ -e FRANKENPHP_CONFIG="worker ./public/index.php 42" \ -v $PWD:/app \ @@ -123,7 +128,7 @@ While it's possible to restart workers [on file changes](config.md#watching-for- gracefully via the [Caddy admin API](https://caddyserver.com/docs/api). If the admin is enabled in your [Caddyfile](config.md#caddyfile-config), you can ping the restart endpoint with a simple POST request like this: -```console +```bash curl -X POST http://localhost:2019/frankenphp/workers/restart ``` diff --git a/docs/x-sendfile.md b/docs/x-sendfile.md index f9513c346d..25039c95f1 100644 --- a/docs/x-sendfile.md +++ b/docs/x-sendfile.md @@ -1,3 +1,8 @@ +--- +title: Serving Large Files with X-Sendfile and X-Accel-Redirect in FrankenPHP +description: Configure FrankenPHP to delegate large static file delivery to the web server after running PHP code, using X-Sendfile or X-Accel-Redirect headers. +--- + # Efficiently Serving Large Static Files (`X-Sendfile`/`X-Accel-Redirect`) Usually, static files can be served directly by the web server, @@ -19,7 +24,7 @@ In the following examples, we assume that the document root of the project is th and that we want to use PHP to serve files stored outside the `public/` directory, from a directory named `private-files/`. -## Configuration +## Configuring X-Accel-Redirect in the FrankenPHP Caddyfile First, add the following configuration to your `Caddyfile` to enable this feature: diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000000..647837d3a9 --- /dev/null +++ b/llms.txt @@ -0,0 +1,56 @@ +# FrankenPHP + +> FrankenPHP is a modern PHP application server written in Go and powered by [Caddy](https://caddyserver.com). It runs PHP applications with automatic HTTPS, HTTP/2 and HTTP/3, an in-memory worker mode, real-time updates via [Mercure](https://mercure.rocks), hot reload, and a static-binary deployment story. + +This index points AI agents and crawlers at the canonical English documentation. All URLs are absolute. Each Markdown source file carries SEO and LLM-friendly YAML frontmatter (`title`, `description`). + +## Core Concepts + +- [FrankenPHP Classic Mode](https://frankenphp.dev/docs/classic/): drop-in replacement for PHP-FPM and Apache mod_php using a fixed or autoscaling thread pool. +- [FrankenPHP Worker Mode](https://frankenphp.dev/docs/worker/): keep your PHP application bootstrapped in memory between requests for lower latency. +- [Configuring FrankenPHP](https://frankenphp.dev/docs/config/): Caddyfile, JSON, environment variables, and `php.ini` configuration. +- [FrankenPHP Internals](https://frankenphp.dev/docs/internals/): thread types, the state machine, the Go/C/PHP CGO boundary, auto-scaling, and per-thread environment sandboxing. + +## Setup and Build + +- [FrankenPHP Docker Image](https://frankenphp.dev/docs/docker/): build custom images, install PHP extensions and Caddy modules, run as non-root, harden with distroless. +- [Compile FrankenPHP From Sources](https://frankenphp.dev/docs/compile/): build on Linux, macOS and FreeBSD with PHP linked as a dynamic library. +- [Static Build](https://frankenphp.dev/docs/static/): create a single self-contained FrankenPHP binary with embedded PHP and Caddy. +- [Embedding PHP Apps as Standalone Binaries](https://frankenphp.dev/docs/embed/): ship a pure-PHP, Symfony or Laravel app as one static executable. +- [Building Images with GitHub Actions](https://frankenphp.dev/docs/github-actions/): automate Docker image builds and publication. + +## Worker Mode and Extensions + +- [FrankenPHP Worker Mode](https://frankenphp.dev/docs/worker/): supervisor configuration, restart policies, and integration with Symfony Runtime and Laravel Octane. +- [Extension Workers](https://frankenphp.dev/docs/extension-workers/): dedicated PHP thread pool from a Go extension for queues, schedulers, and event listeners. +- [Writing PHP Extensions in Go](https://frankenphp.dev/docs/extensions/): extension generator, types API, calling PHP from Go, classes, methods, and constants. + +## Frameworks + +- [Laravel with FrankenPHP](https://frankenphp.dev/docs/laravel/): Docker, local install, Laravel Octane integration, and standalone binaries. +- [Symfony with FrankenPHP](https://frankenphp.dev/docs/symfony/): Symfony Docker, worker mode, hot reload, AssetMapper, and X-Sendfile. +- [WordPress with FrankenPHP](https://frankenphp.dev/docs/wordpress/): minimal install, production Caddyfile, hot reload via Mercure. +- [Migrating from Nginx and PHP-FPM](https://frankenphp.dev/docs/migrate/): step-by-step migration of an existing PHP stack. + +## Real-Time and Performance Features + +- [Mercure Real-Time Hub](https://frankenphp.dev/docs/mercure/): built-in Mercure hub for pushing real-time events to browsers over HTTP. +- [HTTP 103 Early Hints](https://frankenphp.dev/docs/early-hints/): preload critical assets before the final response. +- [Hot Reload](https://frankenphp.dev/docs/hot-reload/): refresh PHP, templates, and frontend assets without manual reload. +- [X-Sendfile and X-Accel-Redirect](https://frankenphp.dev/docs/x-sendfile/): delegate large file delivery to the web server after running PHP. +- [Performance Tuning](https://frankenphp.dev/docs/performance/): thread count, worker mode, glibc vs musl, Go runtime, OPcache, Caddyfile options. + +## Production and Observability + +- [Deploying FrankenPHP in Production](https://frankenphp.dev/docs/production/): Docker Compose deployments with TLS, reverse proxy, and multi-node setups. +- [Known Issues](https://frankenphp.dev/docs/known-issues/): unsupported PHP extensions, musl caveats, Docker TLS gotchas. +- [Observability Overview](https://frankenphp.dev/docs/observability/): Prometheus metrics, structured logs, and the Ember TUI dashboard. +- [Prometheus Metrics](https://frankenphp.dev/docs/metrics/): exposed metrics for threads, workers, queues, crashes, and restarts. +- [Logging](https://frankenphp.dev/docs/logging/): structured logging with `frankenphp_log()` and `error_log()` routed through Caddy. + +## Project + +- [GitHub Repository](https://github.com/php/frankenphp) +- [Docker Hub](https://hub.docker.com/r/dunglas/frankenphp) +- [README](https://github.com/php/frankenphp/blob/main/README.md) +- [Contributing Guide](https://github.com/php/frankenphp/blob/main/CONTRIBUTING.md) From e0b3d68a61e50967e0bd97b3f14a74344f95a3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 13:36:19 +0200 Subject: [PATCH 02/12] docs: address CI and Copilot review feedback - markdownlint: tell MD025 to ignore the YAML frontmatter title so the in-body H1 stops being flagged as a duplicate top-level heading. - gitleaks: refresh the JWT example fingerprint in .gitleaksignore to match the new line number after frontmatter was added to mercure.md. - symfony.md: update the inbound link to x-sendfile.md to point at the new section anchor. - extension-workers.md: fix a pre-existing "a any other goroutine" typo flagged by Copilot. --- .gitleaksignore | 3 +-- .markdown-lint.yaml | 2 ++ docs/extension-workers.md | 2 +- docs/symfony.md | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitleaksignore b/.gitleaksignore index c36609fe58..831e30a21b 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -1,2 +1 @@ -/github/workspace/docs/mercure.md:jwt:88 -/github/workspace/docs/mercure.md:jwt:90 +/github/workspace/docs/mercure.md:jwt:95 diff --git a/.markdown-lint.yaml b/.markdown-lint.yaml index b37e37765f..17f04110fd 100644 --- a/.markdown-lint.yaml +++ b/.markdown-lint.yaml @@ -1,5 +1,7 @@ --- MD010: false MD013: false +MD025: + front_matter_title: "" MD033: false MD060: false diff --git a/docs/extension-workers.md b/docs/extension-workers.md index 5061aa6696..4d1f7296a2 100644 --- a/docs/extension-workers.md +++ b/docs/extension-workers.md @@ -49,7 +49,7 @@ If you are [embedding FrankenPHP in a standard Go application without caddy](htt ## Dispatching Tasks to FrankenPHP Extension Workers -Once the worker pool is active, you can dispatch tasks to it. This can be done inside [native functions exported to PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), or from any Go logic such as a cron scheduler, an event listener (MQTT, Kafka), or a any other goroutine. +Once the worker pool is active, you can dispatch tasks to it. This can be done inside [native functions exported to PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), or from any Go logic such as a cron scheduler, an event listener (MQTT, Kafka), or any other goroutine. ### Headless Mode : `SendMessage` diff --git a/docs/symfony.md b/docs/symfony.md index fba4d4f235..ccabc73b3a 100644 --- a/docs/symfony.md +++ b/docs/symfony.md @@ -132,7 +132,7 @@ The `precompressed` directive tells Caddy to look for pre-compressed versions of FrankenPHP supports [efficiently serving large static files](x-sendfile.md) after executing PHP code (for access control, statistics, etc.). Symfony HttpFoundation [natively supports this feature](https://symfony.com/doc/current/components/http_foundation.html#serving-files). -After [configuring your `Caddyfile`](x-sendfile.md#configuration), it will automatically determine the correct value for the `X-Accel-Redirect` header and add it to the response: +After [configuring your `Caddyfile`](x-sendfile.md#configuring-x-accel-redirect-in-the-frankenphp-caddyfile), it will automatically determine the correct value for the `X-Accel-Redirect` header and add it to the response: ```php use Symfony\Component\HttpFoundation\BinaryFileResponse; From aabbbc0892cf9db6d2e2927cbef35c150f34c59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 13:42:36 +0200 Subject: [PATCH 03/12] docs: drop redundant Caddyfile/Dockerfile filename comments Per review feedback, a bare `# Caddyfile` or `# Dockerfile` on the first line of a `caddyfile` or `dockerfile` fenced block restates information the language tag already carries and just adds noise. Remove those generic headers and keep only the comments that name a specific file (e.g. `# compose.yaml`, `# static-build.Dockerfile`, `// public/index.php`) or describe the block's purpose. --- docs/config.md | 14 -------------- docs/docker.md | 7 ------- docs/migrate.md | 5 ----- docs/production.md | 2 -- docs/symfony.md | 1 - docs/wordpress.md | 2 -- 6 files changed, 31 deletions(-) diff --git a/docs/config.md b/docs/config.md index 44d488d8d2..4ca17995f8 100644 --- a/docs/config.md +++ b/docs/config.md @@ -14,7 +14,6 @@ You can specify a custom path with the `-c` or `--config` option. A minimal `Caddyfile` to serve a PHP application is shown below: ```caddyfile -# Caddyfile # The hostname to respond to localhost @@ -45,7 +44,6 @@ PHP: - You should copy an official template provided by the PHP project: ```dockerfile -# Dockerfile FROM dunglas/frankenphp # Production: @@ -87,7 +85,6 @@ The `php_server` or the `php` [HTTP directives](https://caddyserver.com/docs/cad Minimal example: ```caddyfile -# Caddyfile localhost { # Enable compression (optional) encode zstd br gzip @@ -99,7 +96,6 @@ localhost { You can also explicitly configure FrankenPHP using the [global option](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp`: ```caddyfile -# Caddyfile { frankenphp { num_threads # Sets the number of PHP threads to start. Default: 2x the number of available CPUs. @@ -125,7 +121,6 @@ You can also explicitly configure FrankenPHP using the [global option](https://c Alternatively, you may use the one-line short form of the `worker` option: ```caddyfile -# Caddyfile { frankenphp { worker @@ -138,7 +133,6 @@ Alternatively, you may use the one-line short form of the `worker` option: You can also define multiple workers if you serve multiple apps on the same server: ```caddyfile -# Caddyfile app.example.com { root /path/to/app/public php_server { @@ -166,7 +160,6 @@ it's a PHP file or not. Read more about it in the [performance page](performance Using the `php_server` directive is equivalent to this configuration: ```caddyfile -# Caddyfile route { # Add trailing slash for directory requests @canonicalPath { @@ -190,7 +183,6 @@ route { The `php_server` and the `php` directives have the following options: ```caddyfile -# Caddyfile php_server [] { root # Sets the root folder to the site. Default: `root` directive. split_path # Sets the substrings for splitting the URI into two parts. The first matching substring will be used to split the "path info" from the path. The first piece is suffixed with the matching substring and will be assumed as the actual resource (CGI script) name. The second piece will be set to PATH_INFO for the script to use. Default: `.php` @@ -218,7 +210,6 @@ Workers can instead be restarted on file changes via the `watch` directive. This is useful for development environments. ```caddyfile -# Caddyfile { frankenphp { worker { @@ -237,7 +228,6 @@ where the FrankenPHP process was started. You can instead also specify one or mo [shell filename pattern](https://pkg.go.dev/path/filepath#Match): ```caddyfile -# Caddyfile { frankenphp { worker { @@ -269,7 +259,6 @@ The following example will always serve a file in the public directory if presen and otherwise forward the request to the worker matching the path pattern. ```caddyfile -# Caddyfile { frankenphp { php_server { @@ -294,7 +283,6 @@ But when the fix depends on a third party you don't control, `max_requests` provides a pragmatic and hopefully temporary workaround for production: ```caddyfile -# Caddyfile { frankenphp { max_requests 500 @@ -324,7 +312,6 @@ When set, PHP will load all the file with the `.ini` extension present in the gi You can also change the PHP configuration using the `php_ini` directive in the `Caddyfile`: ```caddyfile -# Caddyfile { frankenphp { php_ini memory_limit 256M @@ -356,7 +343,6 @@ has been read. (for example: [Mercure](mercure.md), WebSocket, Server-Sent Event This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`: ```caddyfile -# Caddyfile { servers { enable_full_duplex diff --git a/docs/docker.md b/docs/docker.md index fb63ee758e..4bbbeed060 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -23,7 +23,6 @@ The tags follow this pattern: `dunglas/frankenphp:-php diff --git a/docs/symfony.md b/docs/symfony.md index ccabc73b3a..3cfaaccce3 100644 --- a/docs/symfony.md +++ b/docs/symfony.md @@ -71,7 +71,6 @@ Hot reloading is enabled by default in [Symfony Docker](https://github.com/dungl To use the [hot reload](hot-reload.md) feature without Symfony Docker, enable [Mercure](mercure.md) and add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`: ```caddyfile -# Caddyfile localhost mercure { diff --git a/docs/wordpress.md b/docs/wordpress.md index f65147a56d..4da4a3df8f 100644 --- a/docs/wordpress.md +++ b/docs/wordpress.md @@ -23,7 +23,6 @@ Run [WordPress](https://wordpress.org/) with FrankenPHP to enjoy a modern, high- For a production-ready setup, prefer using `frankenphp run` with a `Caddyfile` like this one: ```caddyfile -# Caddyfile example.com php_server @@ -36,7 +35,6 @@ log To use the [hot reload](hot-reload.md) feature with WordPress, enable [Mercure](mercure.md) and add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`: ```caddyfile -# Caddyfile localhost mercure { From 30db6ce6f9281611238b9504ba069bbc44ce0bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 13:48:46 +0200 Subject: [PATCH 04/12] docs: restore stable heading anchors and external links - Restore the original `## Environment Variables`, `## PHP config`, and `## Hardening Images` headings. Those were already specific in context, and renaming them broke inbound anchor links from README.md and from migrate.md, performance.md. - Update the worker.md cross-link to symfony.md to point at the new `## Symfony Worker Mode with FrankenPHP` anchor. - Restore the original developer.chrome.com link for the 103 Early Hints reference. The MDN URL swap was an unrequested side-edit. --- docs/config.md | 4 ++-- docs/docker.md | 2 +- docs/early-hints.md | 2 +- docs/worker.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/config.md b/docs/config.md index 4ca17995f8..946b8c3992 100644 --- a/docs/config.md +++ b/docs/config.md @@ -290,7 +290,7 @@ But when the fix depends on a third party you don't control, } ``` -## FrankenPHP Environment Variables +## Environment Variables The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it: @@ -303,7 +303,7 @@ As for FPM and CLI SAPIs, environment variables are exposed by default in the `$ The `S` value of [the `variables_order` PHP directive](https://www.php.net/manual/en/ini.core.php#ini.variables-order) is always equivalent to `ES` regardless of the placement of `E` elsewhere in this directive. -## PHP Configuration in FrankenPHP +## PHP config To load [additional PHP configuration files](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan), the `PHP_INI_SCAN_DIR` environment variable can be used. diff --git a/docs/docker.md b/docs/docker.md index 4bbbeed060..ec49315191 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -210,7 +210,7 @@ The Docker images are built: - when a new release is tagged - daily at 4 am UTC, if new versions of the official PHP images are available -## Hardening FrankenPHP Docker Images +## Hardening Images To further reduce the attack surface and size of your FrankenPHP Docker images, it's also possible to build them on top of a [Google distroless](https://github.com/GoogleContainerTools/distroless) or diff --git a/docs/early-hints.md b/docs/early-hints.md index 40400db32e..304d01d5b5 100644 --- a/docs/early-hints.md +++ b/docs/early-hints.md @@ -5,7 +5,7 @@ description: FrankenPHP natively supports the HTTP 103 Early Hints status code, # Early Hints -FrankenPHP natively supports the [103 Early Hints status code](https://developer.mozilla.org/docs/Web/HTTP/Reference/Status/103). +FrankenPHP natively supports the [103 Early Hints status code](https://developer.chrome.com/blog/early-hints/). Using Early Hints can improve the load time of your web pages by 30%. ```php diff --git a/docs/worker.md b/docs/worker.md index 67df8f6fc2..cbf76c0aa0 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -44,7 +44,7 @@ This feature is often used in combination with [hot reloading](hot-reload.md). ## Worker Mode for Symfony -See [the FrankenPHP Symfony worker mode documentation](symfony.md#worker-mode). +See [the FrankenPHP Symfony worker mode documentation](symfony.md#symfony-worker-mode-with-frankenphp). ## Worker Mode for Laravel Octane From c6e5fe93b4a76d86c40d8a82ca767184f6a94908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 13:55:04 +0200 Subject: [PATCH 05/12] docs: address review feedback on compile.md and embed.md - compile.md: adopt henderkes' frontmatter wording. Use the actual artifact name (libphp.so) and "shared library" instead of the vaguer "PHP as a Library" / "dynamic library". - embed.md: revert two agent-introduced edits that henderkes flagged. Restore the original Laravel link wording and the `### Customizing the Configuration` heading. The previous attempt read awkwardly and implied FrankenPHP itself was being embedded rather than the user's application. --- docs/compile.md | 4 ++-- docs/embed.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/compile.md b/docs/compile.md index aedf3d3290..b6bb6255ef 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -1,6 +1,6 @@ --- -title: Compile FrankenPHP From Sources With PHP as a Library -description: Build FrankenPHP from source on Linux, macOS and FreeBSD, link PHP as a dynamic library via xcaddy or go build, and add custom Caddy modules and extensions. +title: Compile FrankenPHP from sources with libphp.so +description: Build FrankenPHP from source on Linux, macOS and FreeBSD, linking PHP as a shared library via xcaddy or go build, and add custom Caddy modules and extensions. --- # Compile From Sources diff --git a/docs/embed.md b/docs/embed.md index 733a0d43cb..9d66222909 100644 --- a/docs/embed.md +++ b/docs/embed.md @@ -11,7 +11,7 @@ Thanks to this feature, PHP applications can be distributed as standalone binari Learn more about this feature [in the presentation made by Kévin at SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/). -For embedding Laravel applications, see [the Laravel-specific embedding instructions](laravel.md#laravel-apps-as-standalone-binaries). +For embedding Laravel applications, [read this specific documentation entry](laravel.md#laravel-apps-as-standalone-binaries). ## Preparing Your App @@ -47,7 +47,7 @@ composer install --ignore-platform-reqs --no-dev -a composer dump-env prod ``` -### Customizing the Embedded FrankenPHP Configuration +### Customizing the Configuration To customize [the configuration](config.md), you can put a `Caddyfile` as well as a `php.ini` file in the main directory of the app to be embedded (`$TMPDIR/my-prepared-app` in the previous example). From bc9bfacf5916b86191a5ab8d94b9f22ea536431e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 13:58:45 +0200 Subject: [PATCH 06/12] docs: fix remaining "Embedded FrankenPHP" framing in embed.md The binary produced by the embed feature contains FrankenPHP plus the user's PHP application; FrankenPHP itself is not what is being embedded. Drop the misleading "FrankenPHP" qualifier from `## Using the Embedded Binary` so it is consistent with the surrounding headings (`## PHP Extensions in the Embedded Binary`, `## Customizing the Embedded Binary Build`, `## Distributing the Embedded Binary`). --- docs/embed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/embed.md b/docs/embed.md index 9d66222909..b65ecb7e3a 100644 --- a/docs/embed.md +++ b/docs/embed.md @@ -103,7 +103,7 @@ EMBED=/path/to/your/app ./build-static.sh The resulting binary is the file named `frankenphp--` in the `dist/` directory. -## Using the Embedded FrankenPHP Binary +## Using the Embedded Binary This is it! The `my-app` file (or `dist/frankenphp--` on other OSes) contains your self-contained app! From 1fc785e6cedd5f6e6b6c6b436b18f9b40c1ed6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 14:03:15 +0200 Subject: [PATCH 07/12] docs: revert heading renames in embed.md and extension-workers.md Per review, the original heading names already read fine in context: the page title and frontmatter establish the FrankenPHP / extension worker / embedded-app framing, so prefixing every section heading with those qualifiers is redundant. For embed.md, "the Embedded Binary" was also misleading: the binary is the artifact that embeds the user's app, not something that is itself embedded. Restore the main wording for: - ## Using The Binary - ## PHP Extensions - ## Customizing The Build - ## Distributing The Binary - ## Registering the Worker - ### Static Registration - ## Interacting with Workers - ## Worker Script - ## Lifecycle Hooks - ### Example --- docs/embed.md | 8 ++++---- docs/extension-workers.md | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/embed.md b/docs/embed.md index b65ecb7e3a..f4bbdfd32b 100644 --- a/docs/embed.md +++ b/docs/embed.md @@ -103,7 +103,7 @@ EMBED=/path/to/your/app ./build-static.sh The resulting binary is the file named `frankenphp--` in the `dist/` directory. -## Using the Embedded Binary +## Using The Binary This is it! The `my-app` file (or `dist/frankenphp--` on other OSes) contains your self-contained app! @@ -131,18 +131,18 @@ You can also run the PHP CLI scripts embedded in your binary: ./my-app php-cli bin/console ``` -## PHP Extensions in the Embedded Binary +## PHP Extensions By default, the script will build extensions required by the `composer.json` file of your project, if any. If the `composer.json` file doesn't exist, the default extensions are built, as documented in [the static builds entry](static.md). To customize the extensions, use the `PHP_EXTENSIONS` environment variable. -## Customizing the Embedded Binary Build +## Customizing The Build [Read the static build documentation](static.md) to see how to customize the binary (extensions, PHP version...). -## Distributing the Embedded Binary +## Distributing The Binary On Linux, the created binary is compressed using [UPX](https://upx.github.io). diff --git a/docs/extension-workers.md b/docs/extension-workers.md index 4d1f7296a2..d9d998129a 100644 --- a/docs/extension-workers.md +++ b/docs/extension-workers.md @@ -7,9 +7,9 @@ description: Use FrankenPHP Extension Workers to run a dedicated PHP thread pool Extension Workers enable your [FrankenPHP extension](https://frankenphp.dev/docs/extensions/) to manage a dedicated pool of PHP threads for executing background tasks, handling asynchronous events, or implementing custom protocols. Useful for queue systems, event listeners, schedulers, etc. -## Registering a FrankenPHP Extension Worker +## Registering the Worker -### Static Worker Registration +### Static Registration If you don't need to make the worker configurable by the user (fixed script path, fixed number of threads), you can simply register the worker in the `init()` function. @@ -47,7 +47,7 @@ If you plan to share your extension (like a generic queue or event listener), yo If you are [embedding FrankenPHP in a standard Go application without caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), you can register extension workers using `frankenphp.WithExtensionWorkers` when initializing options. -## Dispatching Tasks to FrankenPHP Extension Workers +## Interacting with Workers Once the worker pool is active, you can dispatch tasks to it. This can be done inside [native functions exported to PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), or from any Go logic such as a cron scheduler, an event listener (MQTT, Kafka), or any other goroutine. @@ -117,7 +117,7 @@ func my_worker_http_request(path *C.zend_string) unsafe.Pointer { } ``` -## FrankenPHP Extension Worker PHP Script +## Worker Script The PHP worker script runs in a loop and can handle both raw messages and HTTP requests. @@ -139,7 +139,7 @@ while (frankenphp_handle_request($handler)) { } ``` -## FrankenPHP Extension Worker Lifecycle Hooks +## Lifecycle Hooks FrankenPHP provides hooks to execute Go code at specific points in the lifecycle. @@ -150,7 +150,7 @@ FrankenPHP provides hooks to execute Go code at specific points in the lifecycle | **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Per-thread setup. Called when a thread starts. Receives the Thread ID. | | **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Per-thread cleanup. Receives the Thread ID. | -### Lifecycle Hooks Example +### Example ```go // FrankenPHP extension worker with lifecycle hooks From 69d8eb8ca79459f0335f6c70127a3578ec36be20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 14:12:13 +0200 Subject: [PATCH 08/12] docs: lowercase "The" in embed.md section headings Apply henderkes' suggestion to use lowercase short words ("the") in the three embed.md headings he flagged, and fix the sibling `## Using the Binary` heading the same way so all three remain consistent within the file. --- docs/embed.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/embed.md b/docs/embed.md index f4bbdfd32b..15d6cf1565 100644 --- a/docs/embed.md +++ b/docs/embed.md @@ -103,7 +103,7 @@ EMBED=/path/to/your/app ./build-static.sh The resulting binary is the file named `frankenphp--` in the `dist/` directory. -## Using The Binary +## Using the Binary This is it! The `my-app` file (or `dist/frankenphp--` on other OSes) contains your self-contained app! @@ -138,11 +138,11 @@ If the `composer.json` file doesn't exist, the default extensions are built, as To customize the extensions, use the `PHP_EXTENSIONS` environment variable. -## Customizing The Build +## Customizing the Build [Read the static build documentation](static.md) to see how to customize the binary (extensions, PHP version...). -## Distributing The Binary +## Distributing the Binary On Linux, the created binary is compressed using [UPX](https://upx.github.io). From 389a9121f2e4710fc0fe7b11438f16205f63d7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 14:28:11 +0200 Subject: [PATCH 09/12] docs: apply consistent title case across headings Use Chicago-style title case: capitalize first/last word and all major words; lowercase articles (a, an, the), coordinating conjunctions (and, but, or, nor, for, so, yet), and prepositions regardless of length (with, from, after, between, across, etc.). Also fix the "Mode :" / "Emulation :" stray spaces before colons in extension-workers.md, capitalize "User" in the parenthetical, and title-case "Real-Time", "PHP Config", and the lowercase "packages"/ "binary" / "Component" / "using" cases that slipped in. --- docs/compile.md | 4 ++-- docs/config.md | 12 ++++++------ docs/docker.md | 2 +- docs/embed.md | 2 +- docs/extension-workers.md | 6 +++--- docs/internals.md | 4 ++-- docs/laravel.md | 6 +++--- docs/mercure.md | 2 +- docs/static.md | 2 +- docs/symfony.md | 2 +- docs/worker.md | 2 +- docs/x-sendfile.md | 2 +- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/compile.md b/docs/compile.md index b6bb6255ef..5433a26628 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -1,9 +1,9 @@ --- -title: Compile FrankenPHP from sources with libphp.so +title: Compile FrankenPHP from Sources with libphp.so description: Build FrankenPHP from source on Linux, macOS and FreeBSD, linking PHP as a shared library via xcaddy or go build, and add custom Caddy modules and extensions. --- -# Compile From Sources +# Compile from Sources This document explains how to create a FrankenPHP binary that will load PHP as a dynamic library. This is the recommended method. diff --git a/docs/config.md b/docs/config.md index 946b8c3992..b51baf1fc3 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,5 @@ --- -title: Configuring FrankenPHP With Caddyfile, php.ini, and Env Vars +title: Configuring FrankenPHP with Caddyfile, php.ini, and Env Vars description: Configure FrankenPHP and Caddy via Caddyfile, JSON, or environment variables, including PHP runtime tuning, worker mode, file watching, and module options. --- @@ -53,7 +53,7 @@ RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini ``` -## RPM and Debian packages +## RPM and Debian Packages FrankenPHP: @@ -65,7 +65,7 @@ PHP: - `php.ini`: `/etc/php-zts/php.ini` (a `php.ini` file with production presets is provided by default) - additional configuration files: `/etc/php-zts/conf.d/*.ini` -## Static binary +## Static Binary FrankenPHP: @@ -248,7 +248,7 @@ where the FrankenPHP process was started. You can instead also specify one or mo The file watcher is based on [e-dant/watcher](https://github.com/e-dant/watcher). -## Matching the Worker To a Path +## Matching the Worker to a Path In traditional PHP applications, scripts are always placed in the public directory. This is also true for worker scripts, which are treated like any other PHP script. @@ -271,7 +271,7 @@ and otherwise forward the request to the worker matching the path pattern. } ``` -## Restarting Threads After a Number of Requests (Experimental) +## Restarting Threads after a Number of Requests (Experimental) FrankenPHP can automatically restart PHP threads after they have handled a given number of requests. When a thread reaches the limit, it is fully restarted, @@ -303,7 +303,7 @@ As for FPM and CLI SAPIs, environment variables are exposed by default in the `$ The `S` value of [the `variables_order` PHP directive](https://www.php.net/manual/en/ini.core.php#ini.variables-order) is always equivalent to `ES` regardless of the placement of `E` elsewhere in this directive. -## PHP config +## PHP Config To load [additional PHP configuration files](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan), the `PHP_INI_SCAN_DIR` environment variable can be used. diff --git a/docs/docker.md b/docs/docker.md index ec49315191..63e58ce04c 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -175,7 +175,7 @@ EOF USER ${USER} ``` -### Running With No Capabilities +### Running with No Capabilities Even when running rootless, FrankenPHP needs the `CAP_NET_BIND_SERVICE` capability to bind the web server on privileged ports (80 and 443). diff --git a/docs/embed.md b/docs/embed.md index 15d6cf1565..06c063d401 100644 --- a/docs/embed.md +++ b/docs/embed.md @@ -3,7 +3,7 @@ title: Embedding PHP Apps as Standalone Binaries with FrankenPHP description: How to package a PHP application (including Symfony or Laravel) as a self-contained static binary with FrankenPHP, the PHP interpreter, PHP extensions and Caddy. --- -# PHP Apps As Standalone Binaries +# PHP Apps as Standalone Binaries FrankenPHP has the ability to embed the source code and assets of PHP applications in a static, self-contained binary. diff --git a/docs/extension-workers.md b/docs/extension-workers.md index d9d998129a..19679e6071 100644 --- a/docs/extension-workers.md +++ b/docs/extension-workers.md @@ -39,7 +39,7 @@ func init() { } ``` -### In a Caddy Module (Configurable by the user) +### In a Caddy Module (Configurable by the User) If you plan to share your extension (like a generic queue or event listener), you should wrap it in a Caddy module. This allows users to configure the script path and thread count via their `Caddyfile`. This requires implementing the `caddy.Provisioner` interface and parsing the Caddyfile ([see the frankenphp-queue Caddy module example](https://github.com/dunglas/frankenphp-queue/blob/main/caddy.go)). @@ -51,7 +51,7 @@ If you are [embedding FrankenPHP in a standard Go application without caddy](htt Once the worker pool is active, you can dispatch tasks to it. This can be done inside [native functions exported to PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), or from any Go logic such as a cron scheduler, an event listener (MQTT, Kafka), or any other goroutine. -### Headless Mode : `SendMessage` +### Headless Mode: `SendMessage` Use `SendMessage` to pass raw data directly to your worker script. This is ideal for queues or simple commands. @@ -85,7 +85,7 @@ func my_queue_push(data *C.zval) bool { } ``` -### HTTP Emulation :`SendRequest` +### HTTP Emulation: `SendRequest` Use `SendRequest` if your extension needs to invoke a PHP script that expects a standard web environment (populating `$_SERVER`, `$_GET`, etc.). diff --git a/docs/internals.md b/docs/internals.md index 3d60a06bcf..33975a821b 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -153,7 +153,7 @@ Set(Ready) state is Ready → normal execution ``` -## CGO Boundary Between Go and PHP +## CGO Boundary between Go and PHP ### Exported Go Functions @@ -190,7 +190,7 @@ while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { Bailouts (fatal PHP errors) are caught by `zend_catch`, which marks the thread as unhealthy and forces cleanup. -### Memory Management Across the CGO Boundary +### Memory Management across the CGO Boundary - **Go → C strings**: `C.CString()` allocates with `malloc()`. The C side is responsible for freeing (e.g., `frankenphp_free_request_context()` frees cookie data). - **Go string pinning**: `phpThread` (in `phpthread.go`) embeds Go's [`runtime.Pinner`](https://pkg.go.dev/runtime#Pinner). `thread.Pin()` / `thread.Unpin()` keep Go memory referenced from C alive without copying it. The thread is unpinned after each script execution. diff --git a/docs/laravel.md b/docs/laravel.md index 08cfcc9bcc..ad6fe18c56 100644 --- a/docs/laravel.md +++ b/docs/laravel.md @@ -86,7 +86,7 @@ See also [how to use Mercure with Octane](#mercure-support). Learn more about [Laravel Octane in its official documentation](https://laravel.com/docs/octane). -## Laravel Apps As Standalone Binaries +## Laravel Apps as Standalone Binaries Using [FrankenPHP's application embedding feature](embed.md), it's possible to distribute Laravel apps as standalone binaries. @@ -169,7 +169,7 @@ Your app is now ready! Learn more about the options available and how to build binaries for other OSes in the [applications embedding](embed.md) documentation. -### Changing The Storage Path +### Changing the Storage Path By default, Laravel stores uploaded files, caches, logs, etc. in the application's `storage/` directory. This is not suitable for embedded applications, as each new version will be extracted into a different temporary directory. @@ -205,7 +205,7 @@ You can use [all directives supported by Mercure](https://mercure.rocks/docs/hub To publish and subscribe to updates, we recommend using the [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster) library. Alternatively, see [the Mercure documentation](mercure.md) to do it in pure PHP and JavaScript. -### Running Octane With Standalone Binaries +### Running Octane with Standalone Binaries It's even possible to package Laravel Octane apps as standalone binaries! diff --git a/docs/mercure.md b/docs/mercure.md index abec2994a0..0f73be5da5 100644 --- a/docs/mercure.md +++ b/docs/mercure.md @@ -3,7 +3,7 @@ title: Real-Time Updates with the FrankenPHP Mercure Hub description: FrankenPHP ships with a built-in Mercure hub for pushing real-time events to browsers over HTTP, as a simpler alternative to WebSockets. --- -# Real-time +# Real-Time FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub! Mercure allows you to push real-time events to all the connected devices: they will receive a JavaScript event instantly. diff --git a/docs/static.md b/docs/static.md index 06b7398586..8c199c7e4c 100644 --- a/docs/static.md +++ b/docs/static.md @@ -38,7 +38,7 @@ For better performance in heavily concurrent scenarios, consider using the [mima docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl ``` -### glibc-Based, Mostly Static Build (With Dynamic Extension Support) +### glibc-Based, Mostly Static Build (with Dynamic Extension Support) For a binary that supports loading PHP extensions dynamically while still having the selected extensions compiled statically: diff --git a/docs/symfony.md b/docs/symfony.md index 3cfaaccce3..6a91eee3e1 100644 --- a/docs/symfony.md +++ b/docs/symfony.md @@ -142,7 +142,7 @@ $response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt'); // ... ``` -## Symfony Apps As Standalone Binaries +## Symfony Apps as Standalone Binaries Using [FrankenPHP's application embedding feature](embed.md), it's possible to distribute Symfony apps as standalone binaries. diff --git a/docs/worker.md b/docs/worker.md index cbf76c0aa0..beaa0b08ce 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -115,7 +115,7 @@ docker run \ dunglas/frankenphp ``` -### Restart the Worker After a Certain Number of Requests +### Restart the Worker after a Certain Number of Requests As PHP was not originally designed for long-running processes, there are still many libraries and legacy codes that leak memory. A workaround to using this type of code in worker mode is to restart the worker script after processing a certain number of requests: diff --git a/docs/x-sendfile.md b/docs/x-sendfile.md index 25039c95f1..2feb469102 100644 --- a/docs/x-sendfile.md +++ b/docs/x-sendfile.md @@ -61,6 +61,6 @@ Set the relative file path (from `private-files/`) as the value of the `X-Accel- header('X-Accel-Redirect: file.txt'); ``` -## Projects using the Symfony HttpFoundation component (Symfony, Laravel, Drupal...) +## Projects Using the Symfony HttpFoundation Component (Symfony, Laravel, Drupal...) See [the Symfony documentation](symfony.md#serving-large-static-files-x-sendfile) for details on using this feature with Symfony HttpFoundation. From 09fe160447eab1a8a6d8dde991c2b0857c32e961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 14:31:58 +0200 Subject: [PATCH 10/12] docs: fix grammar and typos across the docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config.md: "all the file" → "all the files"; drop stray "using" in "enable HTTPS using for all the hostnames"; remove a duplicated "start a new shell" line. - docker.md: "by the one contained your custom modules" → "with the one containing your custom modules"; add missing article in "run as a non-root user". - extensions.md: "deeper dive to type juggling" → "into type juggling"; "Forces to design" → "Forces you to design". - known-issues.md: "fully binary" → "fully static binary". - laravel.md: "explicitly the pass `--log-level` option" → "explicitly pass the `--log-level` option"; "you can use enable Mercure" → "you can enable Mercure". - mercure.md: "must used to sign" → "must be used to sign"; "tokens generated aerodynamically using with a trusted" → "generated dynamically using a trusted"; "library handled" → "library handles". - metrics.md: add missing articles in "absolute path of worker file". - static.md: add missing article before "`scratch` Docker image". - worker.md: "number of request" → "number of requests". - x-sendfile.md: replace mid-sentence period with comma so the preamble before the examples reads as one sentence. --- docs/config.md | 6 ++---- docs/docker.md | 4 ++-- docs/extensions.md | 4 ++-- docs/known-issues.md | 2 +- docs/laravel.md | 4 ++-- docs/mercure.md | 6 +++--- docs/metrics.md | 2 +- docs/static.md | 2 +- docs/worker.md | 2 +- docs/x-sendfile.md | 2 +- 10 files changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/config.md b/docs/config.md index b51baf1fc3..61cf4da55e 100644 --- a/docs/config.md +++ b/docs/config.md @@ -307,7 +307,7 @@ The `S` value of [the `variables_order` PHP directive](https://www.php.net/manua To load [additional PHP configuration files](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan), the `PHP_INI_SCAN_DIR` environment variable can be used. -When set, PHP will load all the file with the `.ini` extension present in the given directories. +When set, PHP will load all the files with the `.ini` extension present in the given directories. You can also change the PHP configuration using the `php_ini` directive in the `Caddyfile`: @@ -328,7 +328,7 @@ You can also change the PHP configuration using the `php_ini` directive in the ` ### Disabling HTTPS -By default, FrankenPHP will automatically enable HTTPS using for all the hostnames, including `localhost`. +By default, FrankenPHP will automatically enable HTTPS for all the hostnames, including `localhost`. If you want to disable HTTPS (for example in a development environment), you can set the `SERVER_NAME` environment variable to `http://` or `:80`: Alternatively, you can use all other methods described in the [Caddy documentation](https://caddyserver.com/docs/automatic-https#activation). @@ -446,5 +446,3 @@ Add-Content -Path $PROFILE -Value '. (Join-Path (Split-Path $PROFILE) "frankenph ``` You will need to start a new shell for this setup to take effect. - -You will need to start a new shell for this setup to take effect. diff --git a/docs/docker.md b/docs/docker.md index 63e58ce04c..944501cd19 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -87,7 +87,7 @@ RUN CGO_ENABLED=1 \ FROM dunglas/frankenphp AS runner -# Replace the official binary by the one contained your custom modules +# Replace the official binary with the one containing your custom modules COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp ``` @@ -154,7 +154,7 @@ volumes: ## Running as a Non-Root User -FrankenPHP can run as non-root user in Docker. +FrankenPHP can run as a non-root user in Docker. Here is a sample `Dockerfile` doing this: diff --git a/docs/extensions.md b/docs/extensions.md index dbcda95861..1fcd1c90e1 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -86,7 +86,7 @@ There are two important things to note here: - A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type. - The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go. -While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive to type juggling later in this guide. +While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive into type juggling later in this guide. ### Generating the Extension @@ -339,7 +339,7 @@ type UserStruct struct { - **Method-only interface** - All interactions must go through methods you define - **Better encapsulation** - Internal data structure is completely controlled by Go code - **Type safety** - No risk of PHP code corrupting internal state with wrong types -- **Cleaner API** - Forces to design a proper public interface +- **Cleaner API** - Forces you to design a proper public interface This approach provides better encapsulation and prevents PHP code from accidentally corrupting the internal state of your Go objects. All interactions with the object must go through the methods you explicitly define. diff --git a/docs/known-issues.md b/docs/known-issues.md index 207944f3c3..8a5369e6c8 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -28,7 +28,7 @@ The [get_browser()](https://www.php.net/manual/en/function.get-browser.php) func ## Standalone Binary and Alpine-based Docker Images -The fully binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. +The fully static binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. This may lead to some compatibility issues. In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php) diff --git a/docs/laravel.md b/docs/laravel.md index ad6fe18c56..178d918e36 100644 --- a/docs/laravel.md +++ b/docs/laravel.md @@ -80,7 +80,7 @@ The `octane:frankenphp` command can take the following options: - `--log-level`: Log messages at or above the specified log level, using the native Caddy logger > [!TIP] -> To get structured JSON logs (useful when using log analytics solutions), explicitly the pass `--log-level` option. +> To get structured JSON logs (useful when using log analytics solutions), explicitly pass the `--log-level` option. See also [how to use Mercure with Octane](#mercure-support). @@ -183,7 +183,7 @@ FrankenPHP includes [Mercure support out of the box](mercure.md). If you are not using [Octane](#laravel-octane), see [the Mercure documentation entry](mercure.md). -If you are using Octane, you can use enable Mercure support by adding the following lines to your `config/octane.php` file: +If you are using Octane, you can enable Mercure support by adding the following lines to your `config/octane.php` file: ```php // config/octane.php diff --git a/docs/mercure.md b/docs/mercure.md index 0f73be5da5..08f66f95a4 100644 --- a/docs/mercure.md +++ b/docs/mercure.md @@ -107,19 +107,19 @@ $updateID = file_get_contents('https://localhost/.well-known/mercure', context: error_log("update $updateID published", 4); ``` -The key passed as parameter of the `mercure.publisher_jwt` option in the `Caddyfile` must used to sign the JWT token used in the `Authorization` header. +The key passed as parameter of the `mercure.publisher_jwt` option in the `Caddyfile` must be used to sign the JWT token used in the `Authorization` header. The JWT must include a `mercure` claim with a `publish` permission for the topics you want to publish to. See [the Mercure documentation](https://mercure.rocks/spec#publishers) about authorization. To generate your own tokens, you can use [this jwt.io link](https://www.jwt.io/#token=eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.PXwpfIGng6KObfZlcOXvcnWCJOWTFLtswGI5DZuWSK4), -but for production apps, it's recommended to use short-lived tokens generated aerodynamically using with a trusted [JWT library](https://www.jwt.io/libraries?programming_language=php). +but for production apps, it's recommended to use short-lived tokens generated dynamically using a trusted [JWT library](https://www.jwt.io/libraries?programming_language=php). ### Using Symfony Mercure Alternatively, you can use the [Symfony Mercure Component](https://symfony.com/components/Mercure), a standalone PHP library. -This library handled the JWT generation, update publishing as well as cookie-based authorization for subscribers. +This library handles the JWT generation, update publishing as well as cookie-based authorization for subscribers. First, install the library using Composer: diff --git a/docs/metrics.md b/docs/metrics.md index 2fa6860196..52459878ec 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -22,4 +22,4 @@ When [Caddy metrics](https://caddyserver.com/docs/metrics) are enabled, FrankenP - `frankenphp_worker_restarts{worker="[worker_name]"}`: The number of times a worker has been deliberately restarted. - `frankenphp_worker_queue_depth{worker="[worker_name]"}`: The number of queued requests. -For worker metrics, the `[worker_name]` placeholder is replaced by the worker name in the Caddyfile, otherwise absolute path of worker file will be used. +For worker metrics, the `[worker_name]` placeholder is replaced by the worker name in the Caddyfile, otherwise the absolute path of the worker file will be used. diff --git a/docs/static.md b/docs/static.md index 8c199c7e4c..54dc9e73b5 100644 --- a/docs/static.md +++ b/docs/static.md @@ -10,7 +10,7 @@ it's possible to create a static or mostly static build of FrankenPHP thanks to With this method, a single, portable, binary will contain the PHP interpreter, the Caddy web server, and FrankenPHP! -Fully static native executables require no dependencies at all and can even be run on [`scratch` Docker image](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch). +Fully static native executables require no dependencies at all and can even be run on a [`scratch` Docker image](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch). However, they can't load dynamic PHP extensions (such as Xdebug) and have some limitations because they are using the musl libc. Mostly static binaries only require `glibc` and can load dynamic extensions. diff --git a/docs/worker.md b/docs/worker.md index beaa0b08ce..da1206f9ab 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -120,7 +120,7 @@ docker run \ As PHP was not originally designed for long-running processes, there are still many libraries and legacy codes that leak memory. A workaround to using this type of code in worker mode is to restart the worker script after processing a certain number of requests: -The previous worker snippet allows configuring a maximum number of request to handle by setting an environment variable named `MAX_REQUESTS`. +The previous worker snippet allows configuring a maximum number of requests to handle by setting an environment variable named `MAX_REQUESTS`. ### Restart Workers Manually diff --git a/docs/x-sendfile.md b/docs/x-sendfile.md index 2feb469102..48c7250d65 100644 --- a/docs/x-sendfile.md +++ b/docs/x-sendfile.md @@ -20,7 +20,7 @@ containing the path of the file to be served. FrankenPHP takes care of the rest. This feature is known as **`X-Sendfile`** for Apache, and **`X-Accel-Redirect`** for NGINX. -In the following examples, we assume that the document root of the project is the `public/` directory. +In the following examples, we assume that the document root of the project is the `public/` directory, and that we want to use PHP to serve files stored outside the `public/` directory, from a directory named `private-files/`. From 492f06279b5dd89a888721923d8079dee59dba08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 14:39:31 +0200 Subject: [PATCH 11/12] docs: fix broken Symfony worker anchor link in migrate.md `worker.md#symfony-runtime` never matched a heading on main either: worker.md has a `## Worker Mode for Symfony` section, not a Symfony Runtime section. Point the link at the correct anchor. Verified with a repository-wide audit that resolves every `*.md#anchor` reference in docs/, README.md, CONTRIBUTING.md and SECURITY.md against the actual headings (GitHub-style slugs). After this change, all in-repo anchor links resolve. --- docs/migrate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrate.md b/docs/migrate.md index 7b095917ae..5d8372de53 100644 --- a/docs/migrate.md +++ b/docs/migrate.md @@ -168,7 +168,7 @@ example.com { > [!CAUTION] > -> Worker mode keeps your application in memory between requests. Make sure your code does not rely on global state being reset between requests. Frameworks like [Symfony](worker.md#symfony-runtime), [Laravel](laravel.md#laravel-octane), and [API Platform](https://api-platform.com) have native support for this mode. +> Worker mode keeps your application in memory between requests. Make sure your code does not rely on global state being reset between requests. Frameworks like [Symfony](worker.md#worker-mode-for-symfony), [Laravel](laravel.md#laravel-octane), and [API Platform](https://api-platform.com) have native support for this mode. ## What You Can Remove From b66751fff12cebfd9de1c6fc664b2b3d97cf4354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 5 May 2026 21:27:39 +0200 Subject: [PATCH 12/12] docs: sentence case, "shared library" terminology, Symfony Docker heading - Headings (H1-H4) and frontmatter titles now use sentence case across every English doc and llms.txt: capitalize the first word and proper nouns (FrankenPHP, Symfony, Docker, Caddy, etc.); lowercase common words. Anchors are unchanged because GitHub slugifies case-insensitively. - compile.md: "load PHP as a dynamic library" -> "shared library" (the artifact is libphp.so / libphp.dylib, which is a shared object). Wording about *PHP extensions* still uses "dynamic" since that's the term PHP itself uses for runtime-loaded extensions. - symfony.md: rename `## Running Symfony with the Symfony Docker Image` to `## Running Symfony with Symfony Docker`. Symfony Docker is a template project (Dockerfile, GHA setup, Compose files), not a Docker image. --- docs/classic.md | 4 +-- docs/compile.md | 12 ++++---- docs/config.md | 24 +++++++-------- docs/docker.md | 26 ++++++++-------- docs/embed.md | 20 ++++++------- docs/extension-workers.md | 24 +++++++-------- docs/extensions.md | 62 +++++++++++++++++++-------------------- docs/github-actions.md | 6 ++-- docs/hot-reload.md | 14 ++++----- docs/internals.md | 40 ++++++++++++------------- docs/known-issues.md | 14 ++++----- docs/laravel.md | 14 ++++----- docs/logging.md | 2 +- docs/mercure.md | 8 ++--- docs/metrics.md | 2 +- docs/migrate.md | 14 ++++----- docs/observability.md | 6 ++-- docs/performance.md | 18 ++++++------ docs/production.md | 14 ++++----- docs/static.md | 18 ++++++------ docs/symfony.md | 18 ++++++------ docs/wordpress.md | 4 +-- docs/worker.md | 26 ++++++++-------- docs/x-sendfile.md | 6 ++-- llms.txt | 10 +++---- 25 files changed, 203 insertions(+), 203 deletions(-) diff --git a/docs/classic.md b/docs/classic.md index ffc47db528..a658d9148b 100644 --- a/docs/classic.md +++ b/docs/classic.md @@ -1,9 +1,9 @@ --- -title: FrankenPHP Classic Mode: Drop-in PHP-FPM Replacement +title: FrankenPHP classic mode: drop-in PHP-FPM replacement description: Run FrankenPHP in classic mode as a drop-in replacement for PHP-FPM or Apache mod_php, with a fixed or autoscaling thread pool serving PHP files directly. --- -# Using Classic Mode +# Using classic mode Without any additional configuration, FrankenPHP operates in classic mode. In this mode, FrankenPHP functions like a traditional PHP server, directly serving PHP files. This makes it a seamless drop-in replacement for PHP-FPM or Apache with mod_php. diff --git a/docs/compile.md b/docs/compile.md index 5433a26628..065006a32b 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -1,11 +1,11 @@ --- -title: Compile FrankenPHP from Sources with libphp.so +title: Compile FrankenPHP from sources with libphp.so description: Build FrankenPHP from source on Linux, macOS and FreeBSD, linking PHP as a shared library via xcaddy or go build, and add custom Caddy modules and extensions. --- -# Compile from Sources +# Compile from sources -This document explains how to create a FrankenPHP binary that will load PHP as a dynamic library. +This document explains how to create a FrankenPHP binary that will load PHP as a shared library. This is the recommended method. Alternatively, [fully and mostly static builds](static.md) can also be created. @@ -27,7 +27,7 @@ brew install shivammathur/php/php-zts brotli watcher brew link --overwrite --force shivammathur/php/php-zts ``` -### By Compiling PHP +### By compiling PHP Alternatively, you can compile PHP from sources with the options needed by FrankenPHP by following these steps. @@ -79,7 +79,7 @@ make -j"$(getconf _NPROCESSORS_ONLN)" sudo make install ``` -## Install Optional Dependencies +## Install optional dependencies Some FrankenPHP features depend on optional system dependencies that must be installed. Alternatively, these features can be disabled by passing build tags to the Go compiler. @@ -90,7 +90,7 @@ Alternatively, these features can be disabled by passing build tags to the Go co | Restart workers on file change | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher | | [Mercure](mercure.md) | [Mercure Go library](https://pkg.go.dev/github.com/dunglas/mercure) (automatically installed, AGPL licensed) | nomercure | -## Compile the Go App +## Compile the Go app You can now build the final binary. diff --git a/docs/config.md b/docs/config.md index 61cf4da55e..575d8e12e9 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,5 +1,5 @@ --- -title: Configuring FrankenPHP with Caddyfile, php.ini, and Env Vars +title: Configuring FrankenPHP with Caddyfile, php.ini, and env vars description: Configure FrankenPHP and Caddy via Caddyfile, JSON, or environment variables, including PHP runtime tuning, worker mode, file watching, and module options. --- @@ -53,7 +53,7 @@ RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini ``` -## RPM and Debian Packages +## RPM and Debian packages FrankenPHP: @@ -65,7 +65,7 @@ PHP: - `php.ini`: `/etc/php-zts/php.ini` (a `php.ini` file with production presets is provided by default) - additional configuration files: `/etc/php-zts/conf.d/*.ini` -## Static Binary +## Static binary FrankenPHP: @@ -78,7 +78,7 @@ PHP: - PHP extensions: cannot be loaded, bundle them in the binary itself - copy one of `php.ini-production` or `php.ini-development` provided [in the PHP sources](https://github.com/php/php-src/). -## Caddyfile Config +## Caddyfile config The `php_server` or the `php` [HTTP directives](https://caddyserver.com/docs/caddyfile/concepts#directives) may be used within the site blocks to serve your PHP app. @@ -201,7 +201,7 @@ php_server [] { } ``` -### Watching for File Changes +### Watching for file changes Since workers only boot your application once and keep it in memory, any changes to your PHP files will not be reflected immediately. @@ -248,7 +248,7 @@ where the FrankenPHP process was started. You can instead also specify one or mo The file watcher is based on [e-dant/watcher](https://github.com/e-dant/watcher). -## Matching the Worker to a Path +## Matching the worker to a path In traditional PHP applications, scripts are always placed in the public directory. This is also true for worker scripts, which are treated like any other PHP script. @@ -271,7 +271,7 @@ and otherwise forward the request to the worker matching the path pattern. } ``` -## Restarting Threads after a Number of Requests (Experimental) +## Restarting threads after a number of requests (experimental) FrankenPHP can automatically restart PHP threads after they have handled a given number of requests. When a thread reaches the limit, it is fully restarted, @@ -290,7 +290,7 @@ But when the fix depends on a third party you don't control, } ``` -## Environment Variables +## Environment variables The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it: @@ -303,7 +303,7 @@ As for FPM and CLI SAPIs, environment variables are exposed by default in the `$ The `S` value of [the `variables_order` PHP directive](https://www.php.net/manual/en/ini.core.php#ini.variables-order) is always equivalent to `ES` regardless of the placement of `E` elsewhere in this directive. -## PHP Config +## PHP config To load [additional PHP configuration files](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan), the `PHP_INI_SCAN_DIR` environment variable can be used. @@ -335,7 +335,7 @@ Alternatively, you can use all other methods described in the [Caddy documentati If you want to use HTTPS with the `127.0.0.1` IP address instead of the `localhost` hostname, please read the [known issues](known-issues.md#using-https127001-with-docker) section. -### Full Duplex (HTTP/1) +### Full duplex (HTTP/1) When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body has been read. (for example: [Mercure](mercure.md), WebSocket, Server-Sent Events, etc.) @@ -363,7 +363,7 @@ CADDY_GLOBAL_OPTIONS="servers { You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex). -## Enable the Debug Mode +## Enable the debug mode When using the Docker image, set the `CADDY_GLOBAL_OPTIONS` environment variable to `debug` to enable the debug mode: @@ -374,7 +374,7 @@ docker run -v $PWD:/app/public \ dunglas/frankenphp ``` -## Shell Completion +## Shell completion FrankenPHP provides built-in shell completion support for Bash, Zsh, Fish, and PowerShell. This enables autocompletion for all commands (including custom commands like `php-server`, `php-cli`, and `extension-init`) and their flags. diff --git a/docs/docker.md b/docs/docker.md index 944501cd19..ed39639d10 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,9 +1,9 @@ --- -title: FrankenPHP Docker Image: Build, Configure, Extend +title: FrankenPHP Docker image: build, configure, extend description: Build custom FrankenPHP Docker images, install PHP extensions and Caddy modules, run as non-root, harden with distroless, and enable worker mode by default. --- -# Building Custom Docker Image +# Building a custom Docker image [FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). Debian and Alpine Linux variants are provided for popular architectures. @@ -18,7 +18,7 @@ The tags follow this pattern: `dunglas/frankenphp:-php If you're using Alpine Linux and Symfony, > you may need to [increase the default stack size](compile.md#using-xcaddy). -## Enabling the Worker Mode by Default +## Enabling the worker mode by default Set the `FRANKENPHP_CONFIG` environment variable to start FrankenPHP with a worker script: @@ -111,7 +111,7 @@ FROM dunglas/frankenphp ENV FRANKENPHP_CONFIG="worker ./public/index.php" ``` -## Using a Volume in Development +## Using a volume in development To develop easily with FrankenPHP, mount the directory from your host containing the source code of the app as a volume in the Docker container: @@ -152,7 +152,7 @@ volumes: caddy_config: ``` -## Running as a Non-Root User +## Running as a non-root user FrankenPHP can run as a non-root user in Docker. @@ -175,7 +175,7 @@ EOF USER ${USER} ``` -### Running with No Capabilities +### Running with no capabilities Even when running rootless, FrankenPHP needs the `CAP_NET_BIND_SERVICE` capability to bind the web server on privileged ports (80 and 443). @@ -203,14 +203,14 @@ USER ${USER} Next, set the `SERVER_NAME` environment variable to use an unprivileged port. Example: `:8000` -## FrankenPHP Docker Image Updates +## FrankenPHP Docker image updates The Docker images are built: - when a new release is tagged - daily at 4 am UTC, if new versions of the official PHP images are available -## Hardening Images +## Hardening images To further reduce the attack surface and size of your FrankenPHP Docker images, it's also possible to build them on top of a [Google distroless](https://github.com/GoogleContainerTools/distroless) or @@ -270,7 +270,7 @@ WORKDIR /app ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "--config", "/etc/caddy/Caddyfile"] ``` -## Development Versions +## Development versions Development versions are available in the [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) Docker repository. A new build is triggered every time a commit is pushed to the main branch of the GitHub repository. diff --git a/docs/embed.md b/docs/embed.md index 06c063d401..627a7e3ca4 100644 --- a/docs/embed.md +++ b/docs/embed.md @@ -1,9 +1,9 @@ --- -title: Embedding PHP Apps as Standalone Binaries with FrankenPHP +title: Embedding PHP apps as standalone binaries with FrankenPHP description: How to package a PHP application (including Symfony or Laravel) as a self-contained static binary with FrankenPHP, the PHP interpreter, PHP extensions and Caddy. --- -# PHP Apps as Standalone Binaries +# PHP apps as standalone binaries FrankenPHP has the ability to embed the source code and assets of PHP applications in a static, self-contained binary. @@ -13,7 +13,7 @@ Learn more about this feature [in the presentation made by Kévin at SymfonyCon For embedding Laravel applications, [read this specific documentation entry](laravel.md#laravel-apps-as-standalone-binaries). -## Preparing Your App +## Preparing your app Before creating the self-contained binary be sure that your app is ready for embedding. @@ -47,12 +47,12 @@ composer install --ignore-platform-reqs --no-dev -a composer dump-env prod ``` -### Customizing the Configuration +### Customizing the configuration To customize [the configuration](config.md), you can put a `Caddyfile` as well as a `php.ini` file in the main directory of the app to be embedded (`$TMPDIR/my-prepared-app` in the previous example). -## Creating a Linux Binary +## Creating a Linux binary The easiest way to create a Linux binary is to use the Docker-based builder we provide. @@ -91,7 +91,7 @@ The easiest way to create a Linux binary is to use the Docker-based builder we p The resulting binary is the file named `my-app` in the current directory. -## Creating a Binary for Other OSes +## Creating a binary for other OSes If you don't want to use Docker, or want to build a macOS binary, use the shell script we provide: @@ -103,7 +103,7 @@ EMBED=/path/to/your/app ./build-static.sh The resulting binary is the file named `frankenphp--` in the `dist/` directory. -## Using the Binary +## Using the binary This is it! The `my-app` file (or `dist/frankenphp--` on other OSes) contains your self-contained app! @@ -131,18 +131,18 @@ You can also run the PHP CLI scripts embedded in your binary: ./my-app php-cli bin/console ``` -## PHP Extensions +## PHP extensions By default, the script will build extensions required by the `composer.json` file of your project, if any. If the `composer.json` file doesn't exist, the default extensions are built, as documented in [the static builds entry](static.md). To customize the extensions, use the `PHP_EXTENSIONS` environment variable. -## Customizing the Build +## Customizing the build [Read the static build documentation](static.md) to see how to customize the binary (extensions, PHP version...). -## Distributing the Binary +## Distributing the binary On Linux, the created binary is compressed using [UPX](https://upx.github.io). diff --git a/docs/extension-workers.md b/docs/extension-workers.md index 19679e6071..370be23a41 100644 --- a/docs/extension-workers.md +++ b/docs/extension-workers.md @@ -1,15 +1,15 @@ --- -title: FrankenPHP Extension Workers: Background PHP Thread Pools +title: FrankenPHP extension workers: background PHP thread pools description: Use FrankenPHP Extension Workers to run a dedicated PHP thread pool from a Go extension for queues, schedulers, event listeners, and custom protocols. --- -# Extension Workers +# Extension workers Extension Workers enable your [FrankenPHP extension](https://frankenphp.dev/docs/extensions/) to manage a dedicated pool of PHP threads for executing background tasks, handling asynchronous events, or implementing custom protocols. Useful for queue systems, event listeners, schedulers, etc. -## Registering the Worker +## Registering the worker -### Static Registration +### Static registration If you don't need to make the worker configurable by the user (fixed script path, fixed number of threads), you can simply register the worker in the `init()` function. @@ -39,23 +39,23 @@ func init() { } ``` -### In a Caddy Module (Configurable by the User) +### In a Caddy module (configurable by the user) If you plan to share your extension (like a generic queue or event listener), you should wrap it in a Caddy module. This allows users to configure the script path and thread count via their `Caddyfile`. This requires implementing the `caddy.Provisioner` interface and parsing the Caddyfile ([see the frankenphp-queue Caddy module example](https://github.com/dunglas/frankenphp-queue/blob/main/caddy.go)). -### In a Pure Go Application (Embedding) +### In a pure Go application (embedding) If you are [embedding FrankenPHP in a standard Go application without caddy](https://pkg.go.dev/github.com/dunglas/frankenphp#example-ServeHTTP), you can register extension workers using `frankenphp.WithExtensionWorkers` when initializing options. -## Interacting with Workers +## Interacting with workers Once the worker pool is active, you can dispatch tasks to it. This can be done inside [native functions exported to PHP](https://frankenphp.dev/docs/extensions/#writing-the-extension), or from any Go logic such as a cron scheduler, an event listener (MQTT, Kafka), or any other goroutine. -### Headless Mode: `SendMessage` +### Headless mode: `SendMessage` Use `SendMessage` to pass raw data directly to your worker script. This is ideal for queues or simple commands. -#### Async Queue Extension Example +#### Async queue extension example ```go // FrankenPHP extension: dispatch raw messages to a worker via SendMessage @@ -85,7 +85,7 @@ func my_queue_push(data *C.zval) bool { } ``` -### HTTP Emulation: `SendRequest` +### HTTP emulation: `SendRequest` Use `SendRequest` if your extension needs to invoke a PHP script that expects a standard web environment (populating `$_SERVER`, `$_GET`, etc.). @@ -117,7 +117,7 @@ func my_worker_http_request(path *C.zend_string) unsafe.Pointer { } ``` -## Worker Script +## Worker script The PHP worker script runs in a loop and can handle both raw messages and HTTP requests. @@ -139,7 +139,7 @@ while (frankenphp_handle_request($handler)) { } ``` -## Lifecycle Hooks +## Lifecycle hooks FrankenPHP provides hooks to execute Go code at specific points in the lifecycle. diff --git a/docs/extensions.md b/docs/extensions.md index 1fcd1c90e1..c26f44aa6e 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -1,9 +1,9 @@ --- -title: Writing PHP Extensions in Go with FrankenPHP +title: Writing PHP extensions in Go with FrankenPHP description: Build PHP extensions in Go using FrankenPHP, with an extension generator for boilerplate, a types API for PHP/Go conversion, and support for goroutines. --- -# Writing PHP Extensions in Go +# Writing PHP extensions in Go With FrankenPHP, you can **write PHP extensions in Go**, which allows you to create **high-performance native functions** that can be called directly from PHP. Your applications can leverage any existing or new Go library, as well as the famous concurrency model of **goroutines right from your PHP code**. @@ -11,7 +11,7 @@ Writing PHP extensions is typically done in C, but it's also possible to write t Thanks to Caddy modules, you can write PHP extensions in Go and integrate them very quickly into FrankenPHP. -## Two Approaches +## Two approaches FrankenPHP provides two ways to create PHP extensions in Go: @@ -20,7 +20,7 @@ FrankenPHP provides two ways to create PHP extensions in Go: We'll start with the generator approach as it's the easiest way to get started, then show the manual implementation for those who need complete control. -## Using the Extension Generator +## Using the extension generator FrankenPHP is bundled with a tool that lets you **create a PHP extension** using only Go. **No need to write C code** or use CGO directly: FrankenPHP also includes a **public types API** to help you write your extensions in Go without having to worry about **the type juggling between PHP/C and Go**. @@ -33,7 +33,7 @@ Keep in mind that this tool is **not a full-fledged extension generator**. It is As covered in the manual implementation section below as well, you need to [get the PHP sources](https://www.php.net/downloads.php) and create a new Go module. -#### Create a New Module and Get PHP Sources +#### Create a new module and get PHP sources The first step to writing a PHP extension in Go is to create a new Go module. You can use the following command for this: @@ -47,7 +47,7 @@ The second step is to [get the PHP sources](https://www.php.net/downloads.php) f tar xf php-* ``` -### Writing the Extension +### Writing the extension Everything is now set up to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this: @@ -88,7 +88,7 @@ There are two important things to note here: While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive into type juggling later in this guide. -### Generating the Extension +### Generating the extension This is where the magic happens, and your extension can now be generated. You can run the generator with the following command: @@ -112,7 +112,7 @@ If everything went well, your project directory should contain the following fil > [!IMPORTANT] > **Your source file (`my_extension.go`) is never modified.** The generator creates a separate `_generated.go` file containing CGO wrappers that call your original functions. This means you can safely version control your source file without worrying about generated code polluting it. -### Integrating the Generated Extension into FrankenPHP +### Integrating the generated extension into FrankenPHP Our extension is now ready to be compiled and integrated into FrankenPHP. To do this, refer to the FrankenPHP [compilation documentation](compile.md) to learn how to compile FrankenPHP. Add the module using the `--with` flag, pointing to the path of your module: @@ -128,7 +128,7 @@ xcaddy build \ Note that you point to the `/build` subdirectory that was created during the generation step. However, this is not mandatory: you can also copy the generated files to your module directory and point to it directly. -### Testing Your Generated Extension +### Testing your generated extension You can create a PHP file to test the functions and classes you've created. For example, create an `index.php` file with the following content: @@ -146,7 +146,7 @@ echo $processor->process('Hello World', StringProcessor::MODE_UPPERCASE); // "H Once you've integrated your extension into FrankenPHP as demonstrated in the previous section, you can run this test file using `./frankenphp php-server`, and you should see your extension working. -### Type Juggling +### Type juggling While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding the internals of the Zend Engine and how variables are stored internally in PHP. This table summarizes what you need to know: @@ -279,7 +279,7 @@ func process_data_packed(arr *C.zend_array) unsafe.Pointer { - `frankenphp.GoPackedArray(arr unsafe.Pointer) []any` - Convert a PHP array to a Go slice - `frankenphp.IsPacked(zval *C.zend_array) bool` - Check if a PHP array is packed (indexed only) or associative (key-value pairs) -### Working with Callables +### Working with callables FrankenPHP provides a way to work with PHP callables using the `frankenphp.CallPHPCallable` helper. This allows you to call PHP functions or methods from Go code. @@ -316,7 +316,7 @@ $result = my_array_map(['hello', 'world'], 'strtoupper'); // $result will be ['HELLO', 'WORLD'] ``` -### Declaring a Native PHP Class +### Declaring a native PHP class The generator supports declaring **opaque classes** as Go structs, which can be used to create PHP objects. You can use the `//export_php:class` directive comment to define a PHP class. For example: @@ -331,7 +331,7 @@ type UserStruct struct { } ``` -#### What are Opaque Classes? +#### What are opaque classes? **Opaque classes** are classes where the internal structure (properties) is hidden from PHP code. This means: @@ -343,7 +343,7 @@ type UserStruct struct { This approach provides better encapsulation and prevents PHP code from accidentally corrupting the internal state of your Go objects. All interactions with the object must go through the methods you explicitly define. -#### Adding Methods to Classes +#### Adding methods to classes Since properties are not directly accessible, you **must define methods** to interact with your opaque classes. Use the `//export_php:method` directive to define behavior: @@ -386,7 +386,7 @@ func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) { } ``` -#### Nullable Parameters +#### Nullable parameters The generator supports nullable parameters using the `?` prefix in PHP signatures. When a parameter is nullable, it becomes a pointer in your Go function, allowing you to check if the value was `null` in PHP: @@ -457,11 +457,11 @@ $user->updateInfo(null, 25, null); // Name and active are null This design ensures that your Go code has complete control over how the object's state is accessed and modified, providing better encapsulation and type safety. -### Declaring Constants +### Declaring constants The generator supports exporting Go constants to PHP using two directives: `//export_php:const` for global constants and `//export_php:classconst` for class constants. This allows you to share configuration values, status codes, and other constants between Go and PHP code. -#### Global Constants +#### Global constants Use the `//export_php:const` directive to create global PHP constants: @@ -486,7 +486,7 @@ const ( > > PHP constants will take the name of the Go constant, thus using upper case letters is recommended. -#### Class Constants +#### Class constants Use the `//export_php:classconst ClassName` directive to create constants that belong to a specific PHP class: @@ -595,7 +595,7 @@ func (sp *StringProcessorStruct) Process(input *C.zend_string, mode int64) unsaf } ``` -### Using Namespaces +### Using namespaces The generator supports organizing your PHP extension's functions, classes, and constants under a namespace using the `//export_php:namespace` directive. This helps avoid naming conflicts and provides better organization for your extension's API. @@ -633,7 +633,7 @@ func (u *UserStruct) GetName() unsafe.Pointer { const STATUS_ACTIVE = 1 ``` -#### Using Namespaced Extension in PHP +#### Using namespaced extension in PHP When a namespace is declared, all functions, classes, and constants are placed under that namespace in PHP: @@ -648,22 +648,22 @@ echo $user->getName(); // "John Doe" echo My\Extension\STATUS_ACTIVE; // 1 ``` -#### Important Notes +#### Important notes - Only **one** namespace directive is allowed per file. If multiple namespace directives are found, the generator will return an error. - The namespace applies to **all** exported symbols in the file: functions, classes, methods, and constants. - Namespace names follow PHP namespace conventions using backslashes (`\`) as separators. - If no namespace is declared, symbols are exported to the global namespace as usual. -## Manual Implementation +## Manual implementation If you want to understand how extensions work or need full control over your extension, you can write them manually. This approach gives you complete control but requires more boilerplate code. -### Basic Function +### Basic function We'll see how to write a simple PHP extension in Go that defines a new native function. This function will be called from PHP and will trigger a goroutine that logs a message in Caddy's logs. This function doesn't take any parameters and returns nothing. -#### Define the Go Function +#### Define the Go function In your module, you need to define a new native function that will be called from PHP. To do this, create a file with the name you want, for example, `extension.go`, and add the following code: @@ -719,7 +719,7 @@ php ../php-src/build/gen_stub.php extension.stub.php This script will generate a file named `extension_arginfo.h` that contains the necessary information for PHP to know how to define and call our function. -#### Write the Bridge Between Go and C +#### Write the bridge between Go and C Now, we need to write the bridge between Go and C. Create a file named `extension.h` in your module directory with the following content: @@ -783,11 +783,11 @@ Finally, we define the extension's metadata in a `zend_module_entry` structure, The extension registration is automatically handled by FrankenPHP's `RegisterExtension()` function that we call in our Go code. -### Advanced Usage +### Advanced usage Now that we know how to create a basic PHP extension in Go, let's complexify our example. We will now create a PHP function that takes a string as a parameter and returns its uppercase version. -#### Define the PHP Function Stub +#### Define the PHP function stub To define the new PHP function, we will modify our `extension.stub.php` file to include the new function signature: @@ -827,7 +827,7 @@ static const zend_function_entry ext_functions[] = { We can see that the `go_upper` function is defined with a parameter of type `string` and a return type of `string`. -#### Type Juggling Between Go and PHP/C +#### Type juggling between Go and PHP/C Your Go function cannot directly accept a PHP string as a parameter. You need to convert it to a Go string. Fortunately, FrankenPHP provides helper functions to handle the conversion between PHP strings and Go strings, similar to what we saw in the generator approach. @@ -865,7 +865,7 @@ You can learn more about the `ZEND_PARSE_PARAMETERS_START` and parameters parsin There's only one thing left to do: implement the `go_upper` function in Go. -#### Implement the Go Function +#### Implement the Go function Our Go function will take a `*C.zend_string` as a parameter, convert it to a Go string using FrankenPHP's helper function, process it, and return the result as a new `*C.zend_string`. The helper functions handle all the memory management and conversion complexity for us. @@ -900,7 +900,7 @@ The `false` parameter in `PHPString()` indicates that we want to create a new no > > In this example, we don't perform any error handling, but you should always check that pointers are not `nil` and that the data is valid before using it in your Go functions. -### Integrating the Extension into FrankenPHP +### Integrating the extension into FrankenPHP Our extension is now ready to be compiled and integrated into FrankenPHP. To do this, refer to the FrankenPHP [compilation documentation](compile.md) to learn how to compile FrankenPHP. Add the module using the `--with` flag, pointing to the path of your module: @@ -916,7 +916,7 @@ xcaddy build \ That's it! Your extension is now integrated into FrankenPHP and can be used in your PHP code. -### Testing Your Extension +### Testing your extension After integrating your extension into FrankenPHP, you can create an `index.php` file with examples for the functions you've implemented: diff --git a/docs/github-actions.md b/docs/github-actions.md index 95bc5477bd..e7663a440f 100644 --- a/docs/github-actions.md +++ b/docs/github-actions.md @@ -1,5 +1,5 @@ --- -title: Building FrankenPHP Docker Images with GitHub Actions +title: Building FrankenPHP Docker images with GitHub Actions description: Use GitHub Actions to automatically build and publish FrankenPHP Docker images to a registry on pull requests, merges to main, and tagged releases. --- @@ -17,13 +17,13 @@ In the repository settings, under secrets, add the following secrets: - `REGISTRY_PASSWORD`: The password to use to log in to the registry (e.g. an access key). - `IMAGE_NAME`: The name of the image (e.g. `dunglas/frankenphp`). -## Building and Pushing the Image +## Building and pushing the image 1. Create a Pull Request or push to your fork. 2. GitHub Actions will build the image and run any tests. 3. If the build is successful, the image will be pushed to the registry using the `pr-x`, where `x` is the PR number, as the tag. -## Deploying the Image +## Deploying the image 1. Once the Pull Request is merged, GitHub Actions will again run the tests and build a new image. 2. If the build is successful, the `main` tag will be updated in the Docker registry. diff --git a/docs/hot-reload.md b/docs/hot-reload.md index 65ca9b4004..967fdcc54d 100644 --- a/docs/hot-reload.md +++ b/docs/hot-reload.md @@ -1,9 +1,9 @@ --- -title: FrankenPHP Hot Reload for PHP, Templates, and Assets +title: FrankenPHP hot reload for PHP, templates, and assets description: Use FrankenPHP hot reload to update PHP code, templates, and frontend assets in the browser without manual refresh, with optional DOM morphing via Idiomorph. --- -# Hot Reload +# Hot reload FrankenPHP includes a built-in **hot reload** feature designed to vastly improve the developer experience. @@ -23,7 +23,7 @@ Depending on your setup, the browser will either: - **Morph the DOM** (preserving scroll position and input state) if [Idiomorph](https://github.com/bigskysoftware/idiomorph) is loaded. - **Reload the page** (standard live reload) if Idiomorph is not present. -## Enabling FrankenPHP Hot Reload +## Enabling FrankenPHP hot reload To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`. @@ -83,7 +83,7 @@ php_server { } ``` -## Client-Side Integration for FrankenPHP Hot Reload +## Client-side integration for FrankenPHP hot reload While the server detects changes, the browser needs to subscribe to these events to update the page. FrankenPHP exposes the Mercure Hub URL to use for subscribing to file changes via the `$_SERVER['FRANKENPHP_HOT_RELOAD']` environment variable. @@ -106,7 +106,7 @@ It is available as an [npm](https://www.npmjs.com/package/frankenphp-hot-reload) Alternatively, you can implement your own client-side logic by subscribing directly to the Mercure hub using the `EventSource` native JavaScript class. -### Preserving Existing DOM Nodes +### Preserving existing DOM nodes In rare cases, such as when using development tools [like the Symfony web debug toolbar](https://github.com/symfony/symfony/pull/62970), you may want to preserve specific DOM nodes. @@ -116,7 +116,7 @@ To do so, add the `data-frankenphp-hot-reload-preserve` attribute to the relevan
``` -## Hot Reload with FrankenPHP Worker Mode +## Hot reload with FrankenPHP worker mode If you are running your application in [Worker Mode](https://frankenphp.dev/docs/worker/), your application script remains in memory. This means changes to your PHP code will not be reflected immediately, even if the browser reloads. @@ -144,7 +144,7 @@ php_server { } ``` -## How FrankenPHP Hot Reload Works +## How FrankenPHP hot reload works 1. **Watch**: FrankenPHP monitors the filesystem for modifications using [the `e-dant/watcher` library](https://github.com/e-dant/watcher) under the hood (we contributed the Go binding). 2. **Restart (Worker Mode)**: if `watch` is enabled in the worker config, the PHP worker is restarted to load the new code. diff --git a/docs/internals.md b/docs/internals.md index 33975a821b..97091b8cac 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -1,5 +1,5 @@ --- -title: FrankenPHP Internals: Threads, State Machine, and CGO +title: FrankenPHP internals: threads, state machine, and CGO description: How FrankenPHP works inside: PHP ZTS thread pool, Go state machine, CGO boundary with the PHP SAPI, auto-scaling, and per-thread environment sandboxing. --- @@ -7,7 +7,7 @@ description: How FrankenPHP works inside: PHP ZTS thread pool, Go state machine, This document explains FrankenPHP's internal architecture, focusing on thread management, the state machine, and the CGO boundary between Go and C/PHP. -## FrankenPHP Architecture Overview +## FrankenPHP architecture overview FrankenPHP embeds the PHP interpreter directly into Go via CGO. Each PHP execution runs on a real POSIX thread (not a goroutine) because PHP's ZTS (Zend Thread Safety) model requires it. Go orchestrates these threads through a state machine, while C handles the PHP SAPI lifecycle. @@ -17,9 +17,9 @@ The main layers are: 2. **C layer** (`frankenphp.c`, `frankenphp.h`): PHP SAPI implementation, script execution loop, superglobal management 3. **State machine** (`internal/state/`): Synchronization between Go goroutines and C threads -## FrankenPHP Thread Types +## FrankenPHP thread types -### Main Thread (`phpmainthread.go`) +### Main thread (`phpmainthread.go`) The main PHP thread (`phpMainThread`) initializes the PHP runtime: @@ -30,7 +30,7 @@ The main PHP thread (`phpMainThread`) initializes the PHP runtime: It stays alive for the lifetime of the server. All other threads are started after it signals `Ready`. -### Regular Threads (`threadregular.go`) +### Regular threads (`threadregular.go`) Handle classic one-request-per-invocation PHP scripts. Each request: @@ -39,7 +39,7 @@ Handle classic one-request-per-invocation PHP scripts. Each request: 3. The C layer executes the PHP script 4. `afterScriptExecution()` closes the request context -### Worker Threads (`threadworker.go`) +### Worker threads (`threadworker.go`) Keep a PHP script alive across multiple requests. The PHP script calls `frankenphp_handle_request()` in a loop: @@ -53,11 +53,11 @@ Keep a PHP script alive across multiple requests. The PHP script calls `frankenp After the script exits, the worker is restarted immediately if it had reached `frankenphp_handle_request()` at least once (whether the exit was clean or the result of a fatal error). Exponential backoff is only applied to consecutive startup failures, where the script exits before ever reaching `frankenphp_handle_request()`. -## FrankenPHP Thread State Machine +## FrankenPHP thread state machine Each thread has a `ThreadState` (defined in `internal/state/state.go`) that governs its lifecycle. The state machine uses a `sync.RWMutex` for all state transitions and a channel-based subscriber pattern for blocking waits. -### FrankenPHP Thread States +### FrankenPHP thread states ```text Lifecycle: Reserved → BootRequested → Booting → Inactive → Ready ⇄ (processing) @@ -90,7 +90,7 @@ The full set of states is defined in `internal/state/state.go`: | `TransitionInProgress` | The C thread has acknowledged the transition request. | | `TransitionComplete` | The Go side has installed the new handler. | -### Key State Machine Operations +### Key state machine operations **`RequestSafeStateChange(nextState)`**: The primary way external goroutines request state changes. It: @@ -106,7 +106,7 @@ This guarantees mutual exclusion: only one of `shutdown()`, `setHandler()`, or ` **`CompareAndSwap(compareTo, swapTo)`**: Atomic compare-and-swap. Used for boot initialization. -### Handler Transition Protocol +### Handler transition protocol When a thread needs to change its handler (e.g., from inactive to worker): @@ -129,7 +129,7 @@ Set(TransitionComplete) This protocol ensures the handler pointer is never read and written concurrently. -### Worker Restart Protocol +### Worker restart protocol When workers are restarted (e.g., via admin API): @@ -153,9 +153,9 @@ Set(Ready) state is Ready → normal execution ``` -## CGO Boundary between Go and PHP +## CGO boundary between Go and PHP -### Exported Go Functions +### Exported Go functions C code calls Go functions via CGO exports. The main callbacks are: @@ -174,7 +174,7 @@ C code calls Go functions via CGO exports. The main callbacks are: All these functions receive a `threadIndex` parameter identifying the calling thread. This is a thread-local variable in C (`__thread uintptr_t thread_index`) set during thread initialization. -### C Thread Main Loop +### C thread main loop Each PHP thread runs `php_thread()` in `frankenphp.c`: @@ -190,17 +190,17 @@ while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { Bailouts (fatal PHP errors) are caught by `zend_catch`, which marks the thread as unhealthy and forces cleanup. -### Memory Management across the CGO Boundary +### Memory management across the CGO boundary - **Go → C strings**: `C.CString()` allocates with `malloc()`. The C side is responsible for freeing (e.g., `frankenphp_free_request_context()` frees cookie data). - **Go string pinning**: `phpThread` (in `phpthread.go`) embeds Go's [`runtime.Pinner`](https://pkg.go.dev/runtime#Pinner). `thread.Pin()` / `thread.Unpin()` keep Go memory referenced from C alive without copying it. The thread is unpinned after each script execution. - **PHP memory**: Managed by Zend's memory manager (`emalloc`/`efree`). Automatically freed at request shutdown. -## FrankenPHP Thread Auto-Scaling +## FrankenPHP thread auto-scaling FrankenPHP can automatically scale the number of PHP threads based on demand (`scaling.go`). -### Auto-Scaling Configuration +### Auto-scaling configuration - `num_threads`: Initial number of threads started at boot - `max_threads`: Maximum number of threads allowed (includes auto-scaled) @@ -221,7 +221,7 @@ A dedicated goroutine reads from an unbuffered `scaleChan`: A separate goroutine periodically checks (every 5s) for idle auto-scaled threads. Threads in `Ready` state idle longer than `maxIdleTime` (default 5s) are converted to `Inactive` (up to 10 per cycle). They are not fully stopped: a code path exists for that, but it is currently disabled because some PECL extensions leak memory and prevent threads from cleanly shutting down. -## Per-Thread Environment Sandboxing +## Per-thread environment sandboxing FrankenPHP sandboxes environment variables per-thread: @@ -231,7 +231,7 @@ FrankenPHP sandboxes environment variables per-thread: 4. `frankenphp_putenv()` / `frankenphp_getenv()` operate on a thread-local `sandboxed_env` initialized lazily from `main_thread_env`, preventing race conditions on the global C environment. 5. `reset_sandboxed_environment()` releases `sandboxed_env` after each PHP script execution. In regular mode that's per request; in worker mode it only runs when the worker script itself exits, so `putenv()` writes are visible to subsequent worker requests on the same thread until the script restarts. -## Request Flow (Regular Mode) +## Request flow (regular mode) 1. HTTP request arrives at Caddy 2. FrankenPHP's Caddy module resolves the PHP script path @@ -243,7 +243,7 @@ FrankenPHP sandboxes environment variables per-thread: 8. After execution, `afterScriptExecution()` signals completion 9. The response is sent to the client -## Request Flow (Worker Mode) +## Request flow (worker mode) 1. HTTP request arrives at Caddy 2. FrankenPHP's Caddy module resolves the worker for this request diff --git a/docs/known-issues.md b/docs/known-issues.md index 8a5369e6c8..4dcc1a6498 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -1,11 +1,11 @@ --- -title: FrankenPHP Known Issues, Incompatible Extensions, and Workarounds +title: FrankenPHP known issues, incompatible extensions, and workarounds description: Reference of FrankenPHP limitations, including unsupported PHP extensions, musl libc caveats, Docker TLS setup with 127.0.0.1, and Composer @php scripts. --- -# Known Issues +# Known issues -## Unsupported PHP Extensions +## Unsupported PHP extensions The following extensions are known not to be compatible with FrankenPHP: @@ -14,7 +14,7 @@ The following extensions are known not to be compatible with FrankenPHP: | [imap](https://www.php.net/manual/en/imap.installation.php) | Not thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) | | [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | Not thread-safe | - | -## Buggy PHP Extensions +## Buggy PHP extensions The following extensions have known bugs and unexpected behaviors when used with FrankenPHP: @@ -26,7 +26,7 @@ The following extensions have known bugs and unexpected behaviors when used with The [get_browser()](https://www.php.net/manual/en/function.get-browser.php) function seems to perform badly after a while. A workaround is to cache (e.g. with [APCu](https://www.php.net/manual/en/book.apcu.php)) the results per User Agent, as they are static. -## Standalone Binary and Alpine-based Docker Images +## Standalone binary and Alpine-based Docker images The fully static binary and Alpine-based Docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. This may lead to some compatibility issues. @@ -85,7 +85,7 @@ docker run \ dunglas/frankenphp ``` -## Composer Scripts Referencing `@php` +## Composer scripts referencing `@php` [Composer scripts](https://getcomposer.org/doc/articles/scripts.md) may want to execute a PHP binary for some tasks, e.g. in [a Laravel project](laravel.md) to run `@php artisan package:discover --ansi`. This [currently fails](https://github.com/php/frankenphp/issues/483#issuecomment-1899890915) for two reasons: @@ -118,7 +118,7 @@ export PHP_BINARY=/usr/local/bin/php composer install ``` -## Troubleshooting TLS/SSL Issues with Static Binaries +## Troubleshooting TLS/SSL issues with static binaries When using the static binaries, you may encounter the following TLS-related errors, for instance when sending emails using STARTTLS: diff --git a/docs/laravel.md b/docs/laravel.md index 178d918e36..de9165ca09 100644 --- a/docs/laravel.md +++ b/docs/laravel.md @@ -1,11 +1,11 @@ --- -title: Running Laravel with FrankenPHP (Docker, Octane, Standalone Binary) +title: Running Laravel with FrankenPHP (Docker, Octane, standalone binary) description: How to run a Laravel application with FrankenPHP using the Docker image, a local install, Laravel Octane, or as an embedded standalone binary. --- # Laravel -## Running Laravel with the FrankenPHP Docker Image +## Running Laravel with the FrankenPHP Docker image Serving a [Laravel](https://laravel.com) web application with FrankenPHP is as easy as mounting the project in the `/app` directory of the official Docker image. @@ -17,7 +17,7 @@ docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp And enjoy! -## Installing Laravel with FrankenPHP Locally +## Installing Laravel with FrankenPHP locally Alternatively, you can run your Laravel projects with FrankenPHP from your local machine: @@ -86,7 +86,7 @@ See also [how to use Mercure with Octane](#mercure-support). Learn more about [Laravel Octane in its official documentation](https://laravel.com/docs/octane). -## Laravel Apps as Standalone Binaries +## Laravel apps as standalone binaries Using [FrankenPHP's application embedding feature](embed.md), it's possible to distribute Laravel apps as standalone binaries. @@ -169,14 +169,14 @@ Your app is now ready! Learn more about the options available and how to build binaries for other OSes in the [applications embedding](embed.md) documentation. -### Changing the Storage Path +### Changing the storage path By default, Laravel stores uploaded files, caches, logs, etc. in the application's `storage/` directory. This is not suitable for embedded applications, as each new version will be extracted into a different temporary directory. Set the `LARAVEL_STORAGE_PATH` environment variable (for example, in your `.env` file) or call the `Illuminate\Foundation\Application::useStoragePath()` method to use a directory outside the temporary directory. -### Mercure Support +### Mercure support [Mercure](https://mercure.rocks) is a great way to add real-time capabilities to your Laravel apps. FrankenPHP includes [Mercure support out of the box](mercure.md). @@ -205,7 +205,7 @@ You can use [all directives supported by Mercure](https://mercure.rocks/docs/hub To publish and subscribe to updates, we recommend using the [Laravel Mercure Broadcaster](https://github.com/mvanduijker/laravel-mercure-broadcaster) library. Alternatively, see [the Mercure documentation](mercure.md) to do it in pure PHP and JavaScript. -### Running Octane with Standalone Binaries +### Running Octane with standalone binaries It's even possible to package Laravel Octane apps as standalone binaries! diff --git a/docs/logging.md b/docs/logging.md index 77feb18a84..e8df467aa6 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -1,5 +1,5 @@ --- -title: FrankenPHP Logging with frankenphp_log() and Caddy +title: FrankenPHP logging with frankenphp_log() and Caddy description: Emit structured logs from PHP with frankenphp_log() or error_log() in FrankenPHP, routed through Caddy's logging system as JSON for Datadog, Loki, or Elastic. --- diff --git a/docs/mercure.md b/docs/mercure.md index 08f66f95a4..ebf6541e50 100644 --- a/docs/mercure.md +++ b/docs/mercure.md @@ -1,9 +1,9 @@ --- -title: Real-Time Updates with the FrankenPHP Mercure Hub +title: Real-time updates with the FrankenPHP Mercure hub description: FrankenPHP ships with a built-in Mercure hub for pushing real-time events to browsers over HTTP, as a simpler alternative to WebSockets. --- -# Real-Time +# Real-time FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub! Mercure allows you to push real-time events to all the connected devices: they will receive a JavaScript event instantly. @@ -42,7 +42,7 @@ php_server > > Uncomment the Mercure section in `/etc/frankenphp/Caddyfile` to enable it. -## Subscribing to Updates +## Subscribing to updates By default, the Mercure hub is available on the `/.well-known/mercure` path of your FrankenPHP server. To subscribe to updates, use the native [`EventSource`](https://developer.mozilla.org/docs/Web/API/EventSource) JavaScript class: @@ -59,7 +59,7 @@ To subscribe to updates, use the native [`EventSource`](https://developer.mozill ``` -## Publishing Updates +## Publishing updates ### Using `mercure_publish()` diff --git a/docs/metrics.md b/docs/metrics.md index 52459878ec..a783a5c90e 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -1,5 +1,5 @@ --- -title: FrankenPHP Prometheus Metrics for Threads and Workers +title: FrankenPHP Prometheus metrics for threads and workers description: List of Prometheus-compatible metrics exposed by FrankenPHP via Caddy, covering PHP threads, workers, request times, queue depth, crashes, and restarts. --- diff --git a/docs/migrate.md b/docs/migrate.md index 5d8372de53..29a44f5688 100644 --- a/docs/migrate.md +++ b/docs/migrate.md @@ -8,7 +8,7 @@ description: Step-by-step guide to migrate a PHP application from an Nginx plus FrankenPHP replaces both your web server (Nginx, Apache) and PHP-FPM with a single binary. This guide covers a basic migration for a typical PHP application. -## Key Differences +## Key differences | PHP-FPM setup | FrankenPHP equivalent | | --------------------------------- | -------------------------------------------------------- | @@ -19,7 +19,7 @@ This guide covers a basic migration for a typical PHP application. | `pm = static` / `pm.max_children` | `num_threads` | | `pm = dynamic` | [`max_threads auto`](performance.md#max_threads) | -## Step 1: Replace Your Web Server Config +## Step 1: replace your web server config A typical Nginx + PHP-FPM configuration: @@ -54,7 +54,7 @@ example.com { That's it. The `php_server` directive handles PHP routing, `try_files`-like behavior, and static file serving. -## Step 2: Migrate PHP Configuration +## Step 2: migrate PHP configuration Your existing `php.ini` works as-is. See [Configuration](config.md) for where to place it depending on your installation method. @@ -74,7 +74,7 @@ example.com { } ``` -## Step 3: Adjust Pool Size +## Step 3: adjust pool size In PHP-FPM, you tune `pm.max_children` to control the number of worker processes. In FrankenPHP, the equivalent is `num_threads`: @@ -98,7 +98,7 @@ By default, FrankenPHP starts 2 threads per CPU. For dynamic scaling similar to } ``` -## Step 4: Docker Migration +## Step 4: Docker migration A typical PHP-FPM Docker setup using two containers (Nginx + PHP-FPM) can be replaced by a single container: @@ -150,7 +150,7 @@ If you need additional PHP extensions, see [Building Custom Docker Image](docker For framework-specific Docker setups, see [Symfony Docker](https://github.com/dunglas/symfony-docker) and [running Laravel with the FrankenPHP Docker image](laravel.md#running-laravel-with-the-frankenphp-docker-image). -## Step 5: Consider Worker Mode (Optional) +## Step 5: consider worker mode (optional) In [classic mode](classic.md), FrankenPHP works like PHP-FPM: each request boots the application from scratch. This is a safe starting point for migration. @@ -170,7 +170,7 @@ example.com { > > Worker mode keeps your application in memory between requests. Make sure your code does not rely on global state being reset between requests. Frameworks like [Symfony](worker.md#worker-mode-for-symfony), [Laravel](laravel.md#laravel-octane), and [API Platform](https://api-platform.com) have native support for this mode. -## What You Can Remove +## What you can remove After migrating, you no longer need: diff --git a/docs/observability.md b/docs/observability.md index 750b717008..66508d15e8 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -1,5 +1,5 @@ --- -title: FrankenPHP Observability with Metrics, Logs, and Ember TUI +title: FrankenPHP observability with metrics, logs, and Ember TUI description: Monitor FrankenPHP in development and production using Prometheus metrics, structured logs, the Ember TUI dashboard, and custom Grafana scraping setups. --- @@ -8,7 +8,7 @@ description: Monitor FrankenPHP in development and production using Prometheus m FrankenPHP provides built-in observability features: [Prometheus-compatible metrics](metrics.md) and [structured logging](logging.md). These features, combined with the recommended tools below, give you full visibility into your PHP application's behavior in development and production. -## Ember TUI and Prometheus Exporter +## Ember TUI and Prometheus exporter [Ember](https://github.com/alexandre-daubois/ember) is the most user-friendly way to monitor FrankenPHP. @@ -31,7 +31,7 @@ FrankenPHP integrates with Caddy's logging system and provides `frankenphp_log() See the [Logging](logging.md) page for usage details. -## Custom Prometheus/Grafana Setup +## Custom Prometheus/Grafana setup If you prefer a custom monitoring stack, you can scrape FrankenPHP metrics directly. There are two options: diff --git a/docs/performance.md b/docs/performance.md index e1b5f9540c..97e09e852e 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -1,5 +1,5 @@ --- -title: FrankenPHP Performance Tuning Guide +title: FrankenPHP performance tuning guide description: Tune FrankenPHP for higher throughput and lower latency: thread count, worker mode, glibc vs musl, Go runtime, OPcache, and Caddyfile options. --- @@ -8,7 +8,7 @@ description: Tune FrankenPHP for higher throughput and lower latency: thread cou By default, FrankenPHP tries to offer a good compromise between performance and ease of use. However, it is possible to substantially improve performance using an appropriate configuration. -## Tuning FrankenPHP Threads and Workers +## Tuning FrankenPHP threads and workers By default, FrankenPHP starts 2 times more threads and workers (in worker mode) than the available number of CPU cores. @@ -31,13 +31,13 @@ If set to `auto`, the limit will be estimated based on the `memory_limit` in you `max_threads` is similar to PHP FPM's [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children). The main difference is that FrankenPHP uses threads instead of processes and automatically delegates them across different worker scripts and 'classic mode' as needed. -## Worker Mode for Higher Throughput +## Worker mode for higher throughput Enabling [the FrankenPHP worker mode](worker.md) dramatically improves performance, but your app must be adapted to be compatible with this mode: you need to create a worker script and to be sure that the app is not leaking memory. -## Avoid musl in Production: Prefer glibc Builds +## Avoid musl in production: prefer glibc builds The Alpine Linux variant of the official Docker images and the default binaries we provide are using [the musl libc](https://musl.libc.org). @@ -52,7 +52,7 @@ This can be achieved by using the Debian Docker images, using [our maintainers . For leaner or more secure containers, you may want to consider [a hardened Debian image](docker.md#hardening-images) rather than Alpine. -## Go Runtime Configuration for FrankenPHP +## Go runtime configuration for FrankenPHP FrankenPHP is written in Go. @@ -131,7 +131,7 @@ route { } ``` -## Avoid Caddyfile Placeholders in Hot Paths +## Avoid Caddyfile placeholders in hot paths You can use [placeholders](https://caddyserver.com/docs/conventions#placeholders) in the `root` and `env` directives. However, this prevents caching these values, and comes with a significant performance cost. @@ -152,14 +152,14 @@ php_server { This will improve performance if the `root` directive contains [placeholders](https://caddyserver.com/docs/conventions#placeholders). The gain will be negligible in other cases. -## FrankenPHP Logging Performance +## FrankenPHP logging performance Logging is obviously very useful, but, by definition, it requires I/O operations and memory allocations, which considerably reduces performance. Make sure you [set the logging level](https://caddyserver.com/docs/caddyfile/options#log) correctly, and only log what's necessary. -## PHP Performance Tuning for FrankenPHP +## PHP performance tuning for FrankenPHP FrankenPHP uses the official PHP interpreter. All usual PHP-related performance optimizations apply with FrankenPHP. @@ -174,7 +174,7 @@ In particular: For more details, read [the Symfony performance tuning documentation](https://symfony.com/doc/current/performance.html) (most tips are useful even if you don't use Symfony). -## Splitting the FrankenPHP Thread Pool for Slow Endpoints +## Splitting the FrankenPHP thread pool for slow endpoints It is common for applications to interact with slow external services, like an API that tends to be unreliable under high load or consistently takes 10+ seconds to respond. diff --git a/docs/production.md b/docs/production.md index 9071c6dcf5..b47fdbe9f5 100644 --- a/docs/production.md +++ b/docs/production.md @@ -1,9 +1,9 @@ --- -title: Deploying FrankenPHP in Production with Docker Compose +title: Deploying FrankenPHP in production with Docker Compose description: Deploy a PHP application to production with FrankenPHP and Docker Compose on a single Linux server, including TLS, reverse proxy, and multi-node setups. --- -# Deploying in Production +# Deploying in production In this tutorial, we will learn how to deploy a PHP application on a single server using Docker Compose. @@ -11,7 +11,7 @@ If you're using Symfony, prefer reading the "[Deploy in production](https://gith If you're using API Platform (which also uses FrankenPHP), refer to [the deployment documentation of the framework](https://api-platform.com/docs/deployment/). -## Preparing Your App +## Preparing your app First, create a `Dockerfile` in the root directory of your PHP project: @@ -74,7 +74,7 @@ volumes: Finally, if you use Git, commit these files and push. -## Preparing a Server +## Preparing a server To deploy your application in production, you need a server. In this tutorial, we will use a virtual machine provided by DigitalOcean, but any Linux server can work. @@ -99,7 +99,7 @@ When your Droplet is ready, use SSH to connect: ssh root@ ``` -## Configuring a Domain Name +## Configuring a domain name In most cases, you'll want to associate a domain name with your site. If you don't own a domain name yet, you'll have to buy one through a registrar. @@ -143,7 +143,7 @@ Go to `https://your-domain-name.example.com` and enjoy! > > Docker can have a cache layer, make sure you have the right build for each deployment or rebuild your project with `--no-cache` option to avoid cache issue. -## Running Behind a Reverse Proxy +## Running behind a reverse proxy If FrankenPHP is running behind a reverse proxy or a load balancer (e.g., Nginx, AWS ELB, Google Cloud LB), you must configure the [`trusted_proxies` global option](https://caddyserver.com/docs/caddyfile/options#trusted-proxies) in your Caddyfile @@ -166,7 +166,7 @@ or the [`trustedproxies` middleware](https://laravel.com/docs/trustedproxy) for Without both configurations, headers such as `X-Forwarded-For` and `X-Forwarded-Proto` will be ignored, which can cause issues like incorrect HTTPS detection or wrong client IP addresses. -## Deploying on Multiple Nodes +## Deploying on multiple nodes If you want to deploy your app on a cluster of machines, you can use [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), which is compatible with the provided Compose files. diff --git a/docs/static.md b/docs/static.md index 54dc9e73b5..635a6d8b0b 100644 --- a/docs/static.md +++ b/docs/static.md @@ -1,9 +1,9 @@ --- -title: FrankenPHP Static Build: Single-Binary PHP App Server +title: FrankenPHP static build: single-binary PHP app server description: Create static or mostly static FrankenPHP binaries with embedded PHP and Caddy on Linux and macOS, customize extensions, and load dynamic modules with glibc. --- -# Create a Static Build +# Create a static build Instead of using a local installation of the PHP library, it's possible to create a static or mostly static build of FrankenPHP thanks to the great [static-php-cli project](https://github.com/crazywhalecc/static-php-cli) (despite its name, this project supports all SAPIs, not only CLI). @@ -23,7 +23,7 @@ FrankenPHP also supports [embedding the PHP app in the static binary](embed.md). We provide Docker images to build static Linux binaries: -### musl-Based, Fully Static Build +### musl-based, fully static build For a fully-static binary that runs on any Linux distribution without dependencies but doesn't support dynamic loading of extensions: @@ -38,7 +38,7 @@ For better performance in heavily concurrent scenarios, consider using the [mima docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl ``` -### glibc-Based, Mostly Static Build (with Dynamic Extension Support) +### glibc-based, mostly static build (with dynamic extension support) For a binary that supports loading PHP extensions dynamically while still having the selected extensions compiled statically: @@ -53,7 +53,7 @@ The resulting mostly static (except `glibc`) binary is named `frankenphp` and is If you want to build the static binary without Docker, take a look at the macOS instructions, which also work for Linux. -### Custom PHP Extensions in the Static Build +### Custom PHP extensions in the static build By default, the most popular PHP extensions are compiled. @@ -76,7 +76,7 @@ docker buildx bake \ static-builder-musl ``` -### Extra Caddy Modules +### Extra Caddy modules To add extra Caddy modules or pass other arguments to [xcaddy](https://github.com/caddyserver/xcaddy), use the `XCADDY_ARGS` Docker ARG: @@ -96,7 +96,7 @@ In this example, we add the [Souin](https://souin.io) HTTP cache module for Cadd See also how to [customize the FrankenPHP static build](#customizing-the-frankenphp-static-build) -### GitHub Token +### GitHub token If you hit the GitHub API rate limit, set a GitHub Personal Access Token in an environment variable named `GITHUB_TOKEN`: @@ -117,7 +117,7 @@ cd frankenphp Note: this script also works on Linux (and probably on other Unixes), and is used internally by the Docker images we provide. -## Customizing the FrankenPHP Static Build +## Customizing the FrankenPHP static build The following environment variables can be passed to `docker build` and to the `build-static.sh` script to customize the static build: @@ -134,7 +134,7 @@ script to customize the static build: - `MIMALLOC`: (experimental, Linux-only) replace musl's mallocng by [mimalloc](https://github.com/microsoft/mimalloc) for improved performance. We only recommend using this for musl targeting builds, for glibc prefer disabling this option and using [`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) when you run your binary instead. - `RELEASE`: (maintainers only) when set, the resulting binary will be uploaded on GitHub -## Loading PHP Extensions Dynamically in the Static Binary +## Loading PHP extensions dynamically in the static binary With the glibc or macOS-based binaries, you can load PHP extensions dynamically. However, these extensions will have to be compiled with ZTS support. Since most package managers do not currently offer ZTS versions of their extensions, you will have to compile them yourself. diff --git a/docs/symfony.md b/docs/symfony.md index 6a91eee3e1..4e9588c666 100644 --- a/docs/symfony.md +++ b/docs/symfony.md @@ -1,15 +1,15 @@ --- -title: Running Symfony with FrankenPHP (Docker, Worker Mode, Hot Reload) +title: Running Symfony with FrankenPHP (Docker, worker mode, hot reload) description: How to run a Symfony application with FrankenPHP using Symfony Docker, a local install, worker mode, hot reload, AssetMapper, and X-Sendfile. --- # Symfony -## Running Symfony with the Symfony Docker Image +## Running Symfony with Symfony Docker For [Symfony](https://symfony.com) projects, we recommend using [Symfony Docker](https://github.com/dunglas/symfony-docker), the official Symfony Docker setup maintained by FrankenPHP's author. It provides a complete Docker-based environment with FrankenPHP, automatic HTTPS, HTTP/2, HTTP/3, and worker mode support out of the box. -## Installing Symfony with FrankenPHP Locally +## Installing Symfony with FrankenPHP locally Alternatively, you can run your Symfony projects with FrankenPHP from your local machine: @@ -32,7 +32,7 @@ Alternatively, you can run your Symfony projects with FrankenPHP from your local 3. Start FrankenPHP from the root directory of your Symfony project: `frankenphp run` -## Symfony Worker Mode with FrankenPHP +## Symfony worker mode with FrankenPHP Since Symfony 7.4, FrankenPHP worker mode is natively supported. @@ -55,7 +55,7 @@ docker run \ Learn more about [the worker mode](worker.md). -### Auditing Worker Compatibility +### Auditing worker compatibility [Igor PHP](https://github.com/igor-php/igor-php) is a static linter that scans Symfony projects for state leaks before they bite in production: services missing `ResetInterface`, stateful properties that aren't reset, mutable local statics, `exit()`/`die()` calls, and superglobal writes. It audits your application code as well as services declared in `vendor/`. @@ -64,7 +64,7 @@ composer require --dev igor-php/igor-php vendor/bin/igor-php . ``` -## Hot Reload for Symfony +## Hot reload for Symfony Hot reloading is enabled by default in [Symfony Docker](https://github.com/dunglas/symfony-docker). @@ -97,7 +97,7 @@ Then, add the following code to your `templates/base.html.twig` file: Finally, run `frankenphp run` from the root directory of your Symfony project. -## Pre-Compressing Assets +## Pre-compressing assets Symfony's [AssetMapper component](https://symfony.com/doc/current/frontend/asset_mapper.html) can pre-compress assets with Brotli and Zstandard during deployment. FrankenPHP (through Caddy's `file_server`) can serve these pre-compressed files directly, avoiding on-the-fly compression overhead. @@ -126,7 +126,7 @@ Symfony's [AssetMapper component](https://symfony.com/doc/current/frontend/asset The `precompressed` directive tells Caddy to look for pre-compressed versions of the requested file (e.g., `app.css.zst`, `app.css.br`) and serve them directly if the client supports it. -## Serving Large Static Files (`X-Sendfile`) +## Serving large static files (`X-Sendfile`) FrankenPHP supports [efficiently serving large static files](x-sendfile.md) after executing PHP code (for access control, statistics, etc.). @@ -142,7 +142,7 @@ $response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt'); // ... ``` -## Symfony Apps as Standalone Binaries +## Symfony apps as standalone binaries Using [FrankenPHP's application embedding feature](embed.md), it's possible to distribute Symfony apps as standalone binaries. diff --git a/docs/wordpress.md b/docs/wordpress.md index 4da4a3df8f..c762cc07da 100644 --- a/docs/wordpress.md +++ b/docs/wordpress.md @@ -1,5 +1,5 @@ --- -title: Running WordPress with FrankenPHP (HTTPS, HTTP/3, Hot Reload) +title: Running WordPress with FrankenPHP (HTTPS, HTTP/3, hot reload) description: How to run WordPress with FrankenPHP, including a minimal install, a production Caddyfile, and enabling hot reload via Mercure. --- @@ -30,7 +30,7 @@ encode zstd br gzip log ``` -## Hot Reload for WordPress +## Hot reload for WordPress To use the [hot reload](hot-reload.md) feature with WordPress, enable [Mercure](mercure.md) and add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`: diff --git a/docs/worker.md b/docs/worker.md index da1206f9ab..cd78811a28 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -1,16 +1,16 @@ --- -title: FrankenPHP Worker Mode: Keep Your PHP App in Memory +title: FrankenPHP worker mode: keep your PHP app in memory description: Run FrankenPHP in worker mode to keep your PHP application bootstrapped between requests, cut bootstrap overhead, and serve responses in milliseconds. --- -# Using FrankenPHP Workers +# Using FrankenPHP workers Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds. -## Starting FrankenPHP Worker Scripts +## Starting FrankenPHP worker scripts -### Running a FrankenPHP Worker with Docker +### Running a FrankenPHP worker with Docker Set the value of the `FRANKENPHP_CONFIG` environment variable to `worker /path/to/your/worker/script.php`: @@ -22,7 +22,7 @@ docker run \ dunglas/frankenphp ``` -### Running a FrankenPHP Worker with the Standalone Binary +### Running a FrankenPHP worker with the standalone binary Use the `--worker` option of the `php-server` command to serve the content of the current directory using a worker: @@ -42,15 +42,15 @@ frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to This feature is often used in combination with [hot reloading](hot-reload.md). -## Worker Mode for Symfony +## Worker mode for Symfony See [the FrankenPHP Symfony worker mode documentation](symfony.md#symfony-worker-mode-with-frankenphp). -## Worker Mode for Laravel Octane +## Worker mode for Laravel Octane See [the FrankenPHP Laravel Octane documentation](laravel.md#laravel-octane). -## Writing a Custom FrankenPHP Worker Script +## Writing a custom FrankenPHP worker script The following example shows how to create your own worker script without relying on a third-party library: @@ -115,14 +115,14 @@ docker run \ dunglas/frankenphp ``` -### Restart the Worker after a Certain Number of Requests +### Restart the worker after a certain number of requests As PHP was not originally designed for long-running processes, there are still many libraries and legacy codes that leak memory. A workaround to using this type of code in worker mode is to restart the worker script after processing a certain number of requests: The previous worker snippet allows configuring a maximum number of requests to handle by setting an environment variable named `MAX_REQUESTS`. -### Restart Workers Manually +### Restart workers manually While it's possible to restart workers [on file changes](config.md#watching-for-file-changes), it's also possible to restart all workers gracefully via the [Caddy admin API](https://caddyserver.com/docs/api). If the admin is enabled in your @@ -132,7 +132,7 @@ gracefully via the [Caddy admin API](https://caddyserver.com/docs/api). If the a curl -X POST http://localhost:2019/frankenphp/workers/restart ``` -### Worker Failures +### Worker failures If a worker script crashes with a non-zero exit code, FrankenPHP will restart it with an exponential backoff strategy. If the worker script stays up longer than the last backoff × 2, @@ -151,7 +151,7 @@ frankenphp { } ``` -## Superglobals Behavior +## Superglobals behavior [PHP superglobals](https://www.php.net/manual/en/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...) behave as follows: @@ -179,7 +179,7 @@ However, **`$_ENV` is currently not reset between requests**. This means that any modifications made to `$_ENV` during a request will persist and be visible to subsequent requests handled by the same worker thread. Avoid storing request-specific or sensitive data in `$_ENV`. -## State Persistence +## State persistence Because worker mode keeps the PHP process alive between requests, the following state persists across requests: diff --git a/docs/x-sendfile.md b/docs/x-sendfile.md index 48c7250d65..2a29b2a988 100644 --- a/docs/x-sendfile.md +++ b/docs/x-sendfile.md @@ -1,9 +1,9 @@ --- -title: Serving Large Files with X-Sendfile and X-Accel-Redirect in FrankenPHP +title: Serving large files with X-Sendfile and X-Accel-Redirect in FrankenPHP description: Configure FrankenPHP to delegate large static file delivery to the web server after running PHP code, using X-Sendfile or X-Accel-Redirect headers. --- -# Efficiently Serving Large Static Files (`X-Sendfile`/`X-Accel-Redirect`) +# Efficiently serving large static files (`X-Sendfile`/`X-Accel-Redirect`) Usually, static files can be served directly by the web server, but sometimes it's necessary to execute some PHP code before sending them: @@ -61,6 +61,6 @@ Set the relative file path (from `private-files/`) as the value of the `X-Accel- header('X-Accel-Redirect: file.txt'); ``` -## Projects Using the Symfony HttpFoundation Component (Symfony, Laravel, Drupal...) +## Projects using the Symfony HttpFoundation component (Symfony, Laravel, Drupal...) See [the Symfony documentation](symfony.md#serving-large-static-files-x-sendfile) for details on using this feature with Symfony HttpFoundation. diff --git a/llms.txt b/llms.txt index 647837d3a9..c0a3fde949 100644 --- a/llms.txt +++ b/llms.txt @@ -4,14 +4,14 @@ This index points AI agents and crawlers at the canonical English documentation. All URLs are absolute. Each Markdown source file carries SEO and LLM-friendly YAML frontmatter (`title`, `description`). -## Core Concepts +## Core concepts - [FrankenPHP Classic Mode](https://frankenphp.dev/docs/classic/): drop-in replacement for PHP-FPM and Apache mod_php using a fixed or autoscaling thread pool. - [FrankenPHP Worker Mode](https://frankenphp.dev/docs/worker/): keep your PHP application bootstrapped in memory between requests for lower latency. - [Configuring FrankenPHP](https://frankenphp.dev/docs/config/): Caddyfile, JSON, environment variables, and `php.ini` configuration. - [FrankenPHP Internals](https://frankenphp.dev/docs/internals/): thread types, the state machine, the Go/C/PHP CGO boundary, auto-scaling, and per-thread environment sandboxing. -## Setup and Build +## Setup and build - [FrankenPHP Docker Image](https://frankenphp.dev/docs/docker/): build custom images, install PHP extensions and Caddy modules, run as non-root, harden with distroless. - [Compile FrankenPHP From Sources](https://frankenphp.dev/docs/compile/): build on Linux, macOS and FreeBSD with PHP linked as a dynamic library. @@ -19,7 +19,7 @@ This index points AI agents and crawlers at the canonical English documentation. - [Embedding PHP Apps as Standalone Binaries](https://frankenphp.dev/docs/embed/): ship a pure-PHP, Symfony or Laravel app as one static executable. - [Building Images with GitHub Actions](https://frankenphp.dev/docs/github-actions/): automate Docker image builds and publication. -## Worker Mode and Extensions +## Worker mode and extensions - [FrankenPHP Worker Mode](https://frankenphp.dev/docs/worker/): supervisor configuration, restart policies, and integration with Symfony Runtime and Laravel Octane. - [Extension Workers](https://frankenphp.dev/docs/extension-workers/): dedicated PHP thread pool from a Go extension for queues, schedulers, and event listeners. @@ -32,7 +32,7 @@ This index points AI agents and crawlers at the canonical English documentation. - [WordPress with FrankenPHP](https://frankenphp.dev/docs/wordpress/): minimal install, production Caddyfile, hot reload via Mercure. - [Migrating from Nginx and PHP-FPM](https://frankenphp.dev/docs/migrate/): step-by-step migration of an existing PHP stack. -## Real-Time and Performance Features +## Real-time and performance features - [Mercure Real-Time Hub](https://frankenphp.dev/docs/mercure/): built-in Mercure hub for pushing real-time events to browsers over HTTP. - [HTTP 103 Early Hints](https://frankenphp.dev/docs/early-hints/): preload critical assets before the final response. @@ -40,7 +40,7 @@ This index points AI agents and crawlers at the canonical English documentation. - [X-Sendfile and X-Accel-Redirect](https://frankenphp.dev/docs/x-sendfile/): delegate large file delivery to the web server after running PHP. - [Performance Tuning](https://frankenphp.dev/docs/performance/): thread count, worker mode, glibc vs musl, Go runtime, OPcache, Caddyfile options. -## Production and Observability +## Production and observability - [Deploying FrankenPHP in Production](https://frankenphp.dev/docs/production/): Docker Compose deployments with TLS, reverse proxy, and multi-node setups. - [Known Issues](https://frankenphp.dev/docs/known-issues/): unsupported PHP extensions, musl caveats, Docker TLS gotchas.