From 19af40d4dba50c63d44590606b51a1f6c8212829 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 10 Sep 2026 21:06:24 +0000 Subject: [PATCH 1/8] Enhance OPcache configuration and documentation - Updated default OPcache settings for production mode in various documentation files, emphasizing the importance of `PHP_OPCACHE_ENABLE` and its impact on performance. - Added a new guide on PHP OPcache tuning, detailing how to measure and adjust OPcache settings for optimal application performance. - Clarified the behavior of `PHP_OPCACHE_VALIDATE_TIMESTAMPS` and its implications for development and production environments. - Adjusted Dockerfiles across multiple variations to reflect new OPcache defaults, including increased memory consumption and maximum accelerated files. - Improved scripts and entrypoint messages to provide clearer guidance on OPcache usage and best practices for deployment. --- .../6.default-configurations.md | 2 +- .../docs/2.image-variations/frankenphp.md | 4 +- .../4.using-wordpress-with-docker.md | 4 + .../5.guide/5.major-version-migrations.md | 61 ++++++ .../docs/5.guide/6.php-opcache-tuning.md | 173 ++++++++++++++++++ .../1.environment-variable-specification.md | 20 +- scripts/test-image.sh | 11 +- .../etc/entrypoint.d/0-container-info.sh | 6 +- .../php/conf.d/serversideup-docker-php.ini | 6 +- src/variations/cli/Dockerfile | 10 +- src/variations/fpm-apache/Dockerfile | 10 +- src/variations/fpm-nginx/Dockerfile | 10 +- src/variations/fpm/Dockerfile | 10 +- src/variations/frankenphp/Dockerfile | 10 +- 14 files changed, 298 insertions(+), 39 deletions(-) create mode 100644 docs/content/docs/5.guide/6.php-opcache-tuning.md diff --git a/docs/content/docs/1.getting-started/6.default-configurations.md b/docs/content/docs/1.getting-started/6.default-configurations.md index 609ee6696..f7f628956 100644 --- a/docs/content/docs/1.getting-started/6.default-configurations.md +++ b/docs/content/docs/1.getting-started/6.default-configurations.md @@ -84,7 +84,7 @@ The following extensions are installed by default: | **Extension** | **Description** | **Why we included it** | |---------------|-----------------|------------------------| -| [opcache](https://www.php.net/manual/en/book.opcache.php) | The Zend OPcache provides faster PHP execution through opcode caching and optimization. | This is a must-have for PHP performance.

⚠️ OPcache is disabled by default but can easily be enabled with [`PHP_OPCACHE_ENABLE=1`](/docs/reference/environment-variable-specification).| +| [opcache](https://www.php.net/manual/en/book.opcache.php) | The Zend OPcache provides faster PHP execution through opcode caching and optimization. | This is a must-have for PHP performance.

⚠️ OPcache is disabled by default for development. Set [`PHP_OPCACHE_ENABLE=1`](/docs/reference/environment-variable-specification) for production mode with tuned defaults. See the [OPcache tuning guide](/docs/guide/php-opcache-tuning).| | [mysqli](https://www.php.net/manual/en/book.mysqli.php) | The "MySQL Improved" extension is an older extension for connecting to MySQL 4.1 and above. | **Enabled for fpm-apache only**. This is a legacy MySQL connector required for WordPress.| | [pcntl](https://www.php.net/manual/en/intro.pcntl.php) | Process Control support in PHP implements the Unix style of process creation, program execution, signal handling and process termination. | This is required for [Laravel queues and Laravel Horizon](https://laravel.com/docs/10.x/queues#timeout)| | [pdo_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php) | The MySQL PDO extension allows you to connect to MySQL databases. | MySQL and MariaDB databases are very popular. | diff --git a/docs/content/docs/2.image-variations/frankenphp.md b/docs/content/docs/2.image-variations/frankenphp.md index f432b9277..936bd7762 100644 --- a/docs/content/docs/2.image-variations/frankenphp.md +++ b/docs/content/docs/2.image-variations/frankenphp.md @@ -370,8 +370,8 @@ For a complete list of available environment variables, see the [Environment Var | `PHP_MAX_FILE_UPLOADS` | `20` | Maximum number of files per request | | `PHP_POST_MAX_SIZE` | `100M` | Maximum POST request size | | `PHP_OPCACHE_ENABLE` | `0` | Enable OPcache (`0`/`1`) | -| `PHP_OPCACHE_REVALIDATE_FREQ` | `2` | How often to check for file changes (seconds) | -| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `1` | Whether to validate timestamps (`0`/`1`) | +| `PHP_OPCACHE_REVALIDATE_FREQ` | `2` | How often to check for file changes (seconds), only when timestamps are validated | +| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `0` | Whether to check files for changes (`0`/`1`). Set to `1` when mounting code as a volume with OPcache enabled | ## Caddy Configuration FrankenPHP uses Caddy's configuration format (Caddyfile) instead of NGINX configuration. diff --git a/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md b/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md index 790ee30c5..e250cf52e 100644 --- a/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md +++ b/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md @@ -156,6 +156,10 @@ git pull origin main This approach prioritizes plugin compatibility over modern deployment practices. It works reliably with the widest range of WordPress plugins while still benefiting from containerized infrastructure. :: +::warning +With `PHP_OPCACHE_ENABLE=1`, PHP files are cached until the container restarts because [`PHP_OPCACHE_VALIDATE_TIMESTAMPS`](/docs/reference/environment-variable-specification) defaults to `0`. Updates made through the WordPress admin still work because WordPress clears the cache for the files it writes. Changes made with `git pull`, WP-CLI, SFTP, or plugins that write PHP files directly are not picked up until you restart the container. If you deploy this way, either restart the container after each update or set `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1`. [Learn more in the OPcache tuning guide →](/docs/guide/php-opcache-tuning) +:: + ### Which approach should you choose? - **Volume-based**: Best for sites heavily dependent on third-party plugins or when you need maximum WordPress ecosystem compatibility diff --git a/docs/content/docs/5.guide/5.major-version-migrations.md b/docs/content/docs/5.guide/5.major-version-migrations.md index ebc18aab8..4214b6b36 100644 --- a/docs/content/docs/5.guide/5.major-version-migrations.md +++ b/docs/content/docs/5.guide/5.major-version-migrations.md @@ -23,6 +23,67 @@ Debian Bullseye and Alpine 3.16 were dropped at the same time. Debian 11 reached If you are on one of these, move to PHP 8.2 or newer on `bookworm`, `trixie`, `alpine3.23`, or `alpine3.24`. See [EOL versions and the legacy-modernization path](https://github.com/serversideup/docker-php/blob/main/SECURITY.md#eol-versions-and-the-legacy-modernization-path). +## Version 4 → Version 5 Migration +Version 5 is about OPcache. Setting `PHP_OPCACHE_ENABLE=1` now gives you a production-tuned configuration out of the box instead of PHP's stock defaults. There is one breaking change, and it only affects you if you run with OPcache enabled while your code is mounted as a volume. + +If you want to stay on Version 4 while you review the changes, pin your image tag to `v4`: + +```yml [compose.yml] {3} +services: + php: + image: serversideup/php:8.5-fpm-nginx-v4 +``` + +### Why we changed OPcache +Most people start with these images in development, so OPcache stays off by default to keep your edits showing up instantly. But when you flip it on for production, the settings behind it should be the ones you would have picked yourself after reading the docs. They were not. Version 4 checked every cached file for changes every two seconds, shipped memory sizes that a modern Laravel or Symfony application outgrows, and documented an environment variable that did nothing. Version 5 fixes all of that. [Read the OPcache tuning guide →](/docs/guide/php-opcache-tuning) + +### Breaking changes in Version 5 +::caution +The following change alters behavior for existing configurations that set `PHP_OPCACHE_ENABLE=1`. +:: + +#### `PHP_OPCACHE_VALIDATE_TIMESTAMPS` now defaults to `0` +With OPcache enabled, PHP files are now cached until the container restarts. PHP no longer checks the filesystem for changes on every request. This is the correct setting for code that is built into the image, which is how we recommend deploying. + +You are affected if you set `PHP_OPCACHE_ENABLE=1` **and** any of these apply: + +- Your code is mounted as a volume and you edit it in place +- You follow the [volume-based WordPress approach](/docs/framework-guides/wordpress/using-wordpress-with-docker#volume-based-approach-traditional) and update with `git pull`, WP-CLI, or SFTP +- You run commands like `docker exec php artisan optimize` against a live container and expect the web workers to pick up the new files + +The fix is one of two things: restart the container after code changes (recommended), or set `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1` to restore the Version 4 behavior. + +#### `PHP_OPCACHE_FORCE_RESTART_TIMEOUT` is now applied +This variable existed in Version 4 but never reached `php.ini`. It now works. The default of `180` matches PHP's own default, so nothing changes unless you had set it to something else expecting an effect. + +### Changed defaults +These apply only when `PHP_OPCACHE_ENABLE=1`. They are sized for modern applications and the memory is only used as files are cached. + +| Variable | Version 4 | Version 5 | +|----------|-----------|-----------| +| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `1` | `0` | +| `PHP_OPCACHE_MEMORY_CONSUMPTION` | `128` | `256` | +| `PHP_OPCACHE_INTERNED_STRINGS_BUFFER` | `8` | `16` | +| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `10000` | `20000` | + +### New variables +- `PHP_OPCACHE_PRELOAD` - Path to a preload script. Preloading is one of the biggest wins available for Symfony applications. +- `PHP_OPCACHE_PRELOAD_USER` - The user to preload as when the container runs as root. + +[See the full list of environment variables →](/docs/reference/environment-variable-specification) + +### V5 Migration Checklist + +#### Docker Compose +- Update the image tag +- If your code is mounted as a volume with `PHP_OPCACHE_ENABLE=1`, either turn OPcache off for that environment or add `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1` +- If you deploy WordPress on a volume, add `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1` or restart the container after updates made outside the WordPress admin +- Replace any `docker exec ... artisan optimize` style deployment steps with a container restart + +#### Dockerfile +- Nothing is required +- If you add `PHP_OPCACHE_PRELOAD`, prefer setting it on the running service rather than as an `ENV` in the Dockerfile, so build steps like `RUN composer install` do not depend on the preload script + ## Version 3 → Version 4 Migration Version 3 to Version 4 is a much easier migration compared to previous versions. There are **no breaking changes**, so you can simply update your image tag to the latest version and take advantage of the new features. diff --git a/docs/content/docs/5.guide/6.php-opcache-tuning.md b/docs/content/docs/5.guide/6.php-opcache-tuning.md new file mode 100644 index 000000000..810e76b9f --- /dev/null +++ b/docs/content/docs/5.guide/6.php-opcache-tuning.md @@ -0,0 +1,173 @@ +--- +head.title: 'PHP OPcache Tuning - Docker PHP - Server Side Up' +description: 'Learn how OPcache works in serversideup/php, what production mode changes, and how to measure and tune it for your application.' +layout: docs +title: PHP OPcache tuning +--- + +::lead-p +OPcache is the single biggest performance win available to a PHP application. It compiles your PHP files once and keeps the result in shared memory, so PHP skips reading and compiling code on every request. This guide explains how our images configure it, what the defaults mean, and how to tune them when your app needs more. +:: + +## Development mode vs production mode + +OPcache is controlled by a single switch, [`PHP_OPCACHE_ENABLE`](/docs/reference/environment-variable-specification). + +| Mode | Setting | What happens | +|------|---------|--------------| +| Development | `PHP_OPCACHE_ENABLE=0` (default) | OPcache is off. Every request reads your files fresh, so edits show up instantly when your code is mounted as a volume. | +| Production | `PHP_OPCACHE_ENABLE=1` | OPcache is on with defaults tuned for containers: files are compiled once and cached until the container restarts. | + +We keep OPcache off by default so nobody loses an afternoon wondering why their change is not showing up. When you build your production image, turn it on: + +```yml [compose.yml] {5} +services: + php: + image: serversideup/php:8.5-fpm-nginx + environment: + PHP_OPCACHE_ENABLE: "1" +``` + +::note +`PHP_OPCACHE_ENABLE` sets both `opcache.enable` and `opcache.enable_cli`, so CLI commands such as `php artisan` also run with OPcache. This helps long-running CLI processes like queue workers and Horizon. +:: + +## What production mode gives you + +When `PHP_OPCACHE_ENABLE=1`, these are the defaults you get: + +| Variable | Default | What it controls | +|----------|---------|------------------| +| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `0` | Whether OPcache checks if a file changed. `0` means files are cached until the container restarts. | +| `PHP_OPCACHE_REVALIDATE_FREQ` | `2` | How many seconds between change checks. Ignored unless timestamps are validated. | +| `PHP_OPCACHE_MEMORY_CONSUMPTION` | `256` | Size of the shared memory segment in megabytes. | +| `PHP_OPCACHE_INTERNED_STRINGS_BUFFER` | `16` | Megabytes reserved inside that segment for interned strings. | +| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `20000` | Maximum number of files that can be cached. | +| `PHP_OPCACHE_FORCE_RESTART_TIMEOUT` | `180` | Seconds to wait for a stuck cache restart before OPcache forces it. | +| `PHP_OPCACHE_SAVE_COMMENTS` | `1` | Keep PHPDoc comments in the cache. Required by Doctrine, PHPUnit, and many Laravel packages. | +| `PHP_OPCACHE_ENABLE_FILE_OVERRIDE` | `0` | Let OPcache answer `file_exists()` from its cache. Left off because it reports deleted files as present when timestamps are not validated. | +| `PHP_OPCACHE_JIT` | `off` | The JIT compiler. See [JIT](#jit) below. | +| `PHP_OPCACHE_JIT_BUFFER_SIZE` | `0` | Memory reserved for JIT code. | +| `PHP_OPCACHE_PRELOAD` | `""` | Path to a preload script. See [Preloading](#preloading) below. | +| `PHP_OPCACHE_PRELOAD_USER` | `""` | User to run the preload script as. Only needed when running as root. | + +The most important of these is `PHP_OPCACHE_VALIDATE_TIMESTAMPS=0`. With it, PHP never touches the filesystem to check whether a cached file changed. This is exactly what you want when your code is built into the image: the files cannot change without a new container anyway, so checking is wasted work. + +## How OPcache uses memory + +OPcache reserves one shared memory segment of `PHP_OPCACHE_MEMORY_CONSUMPTION` megabytes when PHP starts. The interned strings buffer and the JIT buffer are carved out of that segment, not added to it. With the defaults, 256 MB is reserved, 16 MB of it holds interned strings, and the remaining 240 MB holds compiled code. + +Reserving memory is not the same as using it. The segment is mapped lazily, so the container only pays for pages that are actually written. A typical Laravel application caches somewhere between 60 MB and 150 MB. Every PHP-FPM worker shares the same segment, so the cost does not multiply with the number of workers. + +::tip +If you are sizing a small container, the number that matters is `PHP_FPM_PM_MAX_CHILDREN` multiplied by `PHP_MEMORY_LIMIT`, not OPcache. Twenty workers with a 256M limit can use far more than OPcache ever will. +:: + +## Measuring what your app needs + +Guessing is unnecessary. PHP tells you exactly how full the cache is. Drop this into a route or run it through the web server, since the CLI has its own separate cache: + +```php [public/opcache-status.php] +*Default: "-1"*|This sets the maximum time in seconds a script is allowed to parse input data, like POST and GET. Timing begins at the moment PHP is invoked at the server and ends when execution begins. The default setting is -1, which means that max_execution_time is used instead. Set to 0 to allow unlimited time. This directive is hardcoded to -1 for the CLI SAPI by PHP. (Official docs)|all `PHP_MAX_INPUT_VARS`
*Default: "1000"*|Set the limits for number of input variables (e.g., POST, GET, or COOKIE variables) that PHP will process in a single request. (Official docs)|all `PHP_MEMORY_LIMIT`
*Default: "256M"*|Set the maximum amount of memory in bytes that a script is allowed to allocate. (Official docs)|all -`PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. ⚠️ This will set **both values** for `opcache.enable` and `opcache.enable_cli`. (Official docs)|all +`PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. `0` is development mode and `1` is production mode with the tuned defaults below. ⚠️ This will set **both values** for `opcache.enable` and `opcache.enable_cli`, so CLI commands like `php artisan` use OPcache too. See the [OPcache tuning guide](/docs/guide/php-opcache-tuning). (Official docs)|all `PHP_OPCACHE_ENABLE_FILE_OVERRIDE`
*Default: "0"*|Enable or disable file existence override (file_exists, etc.). (Official docs)|all `PHP_OPCACHE_FORCE_RESTART_TIMEOUT`
*Default: "180"*|The number of seconds to wait for a scheduled restart to begin if the cache isn't active, in seconds. If the timeout is hit, then OPcache assumes that something is wrong and will kill the processes holding locks on the cache to permit a restart. (Official docs)|all -`PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "8"*|The amount of memory used to store interned strings, in megabytes. (Official docs)|all -`PHP_OPCACHE_JIT`
*Default: "off"*|Enable or disable the JIT compiler. (Official docs)|all -`PHP_OPCACHE_JIT_BUFFER_SIZE`
*Default: "0"*|The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. (Official docs)|all -`PHP_OPCACHE_MAX_ACCELERATED_FILES`
*Default: "10000"*|The maximum number of keys (scripts) in the OPcache hash table. (Official docs)|all -`PHP_OPCACHE_MEMORY_CONSUMPTION`
*Default: "128"*|The amount of memory used by the OPcache engine, in megabytes. (Official docs)|all -`PHP_OPCACHE_REVALIDATE_FREQ`
*Default: "2"*|How often the OPcache checks for updates to cached files (in seconds). (Official docs)|all -`PHP_OPCACHE_SAVE_COMMENTS`
*Default: "1"*|Remove comments from OPcache to minify a bit further. Note: any code that depends on PHPDoc annotations can break from this. (Official docs)|all -`PHP_OPCACHE_VALIDATE_TIMESTAMPS`
*Default: "1"*|Whether OPcache checks for changes to files, or requires reload of PHP to revalidate OPcache. (Official docs)|all +`PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "16"*|The amount of memory used to store interned strings, in megabytes. This is reserved inside `PHP_OPCACHE_MEMORY_CONSUMPTION`, not in addition to it. (Official docs)|all +`PHP_OPCACHE_JIT`
*Default: "off"*|Enable or disable the JIT compiler. To turn it on, set this to `tracing` **and** set `PHP_OPCACHE_JIT_BUFFER_SIZE` to a non-zero value like `64M`. (Official docs)|all +`PHP_OPCACHE_JIT_BUFFER_SIZE`
*Default: "0"*|The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. This is reserved inside `PHP_OPCACHE_MEMORY_CONSUMPTION`. (Official docs)|all +`PHP_OPCACHE_MAX_ACCELERATED_FILES`
*Default: "20000"*|The maximum number of keys (scripts) in the OPcache hash table. (Official docs)|all +`PHP_OPCACHE_MEMORY_CONSUMPTION`
*Default: "256"*|The amount of shared memory reserved for OPcache, in megabytes. Memory is only used as files are cached, so a larger value costs nothing until it is needed. (Official docs)|all +`PHP_OPCACHE_PRELOAD`
*Default: ""*|Path to a PHP script that OPcache compiles and runs at startup (preloading). Empty disables preloading. ⚠️ Since `PHP_OPCACHE_ENABLE` also enables OPcache for the CLI, the script runs on every `php` command too. Preloading as root requires `PHP_OPCACHE_PRELOAD_USER`. See the [OPcache tuning guide](/docs/guide/php-opcache-tuning). (Official docs)|all +`PHP_OPCACHE_PRELOAD_USER`
*Default: ""*|The system user to run the preload script as. Only needed when the web server runs as root, since PHP refuses to preload as root without it. (Official docs)|all +`PHP_OPCACHE_REVALIDATE_FREQ`
*Default: "2"*|How often the OPcache checks for updates to cached files (in seconds). Only applies when `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1`. (Official docs)|all +`PHP_OPCACHE_SAVE_COMMENTS`
*Default: "1"*|Keep PHPDoc comments in the cached code. Setting this to `0` saves a little memory but breaks any code that reads PHPDoc annotations at runtime (Doctrine, PHPUnit, and many Laravel packages). (Official docs)|all +`PHP_OPCACHE_VALIDATE_TIMESTAMPS`
*Default: "0"*|Whether OPcache checks for changes to files. With `0`, PHP files are cached until the container restarts, which is ideal for immutable containers. Set to `1` if you mount your code as a volume and still want OPcache enabled. (Official docs)|all `PHP_OPEN_BASEDIR`
*Default: "None"* |Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself. `open_basedir` is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed. (Official docs)| all `PHP_POST_MAX_SIZE`
*Default: "100M"*|Sets max size of post data allowed. (Official docs)|all `PHP_REALPATH_CACHE_SIZE`
*Default: "4096K"*|Size of the realpath cache. Applications with many files (large `vendor/` directories) may benefit from a larger cache. Note: the cache is disabled when `PHP_OPEN_BASEDIR` is set. (Official docs)|all diff --git a/scripts/test-image.sh b/scripts/test-image.sh index fe73ae9cd..cea5ccec1 100755 --- a/scripts/test-image.sh +++ b/scripts/test-image.sh @@ -48,13 +48,16 @@ pass "Extensions loaded: $expected_extensions" # PHP_* environment variables reach php.ini through ${VAR} substitution. Override a few # of the different value types (size, boolean, list) and confirm PHP sees them. +# OPcache is enabled so the CLI SAPI also allocates the shared cache with the production defaults. ini_values=$(docker run --rm \ --env PHP_MEMORY_LIMIT=512M \ --env PHP_REALPATH_CACHE_SIZE=8M \ --env PHP_SESSION_COOKIE_HTTPONLY=0 \ --env PHP_DISABLE_FUNCTIONS=shell_exec \ - "$image" php -r 'echo ini_get("memory_limit"), " ", ini_get("realpath_cache_size"), " ", ini_get("session.cookie_httponly"), " ", ini_get("disable_functions");' | tail -n1) -[ "$ini_values" = "512M 8M 0 shell_exec" ] || fail "PHP_* environment variables did not apply to php.ini. Got: $ini_values" + --env PHP_OPCACHE_ENABLE=1 \ + --env PHP_OPCACHE_FORCE_RESTART_TIMEOUT=60 \ + "$image" php -r 'echo ini_get("memory_limit"), " ", ini_get("realpath_cache_size"), " ", ini_get("session.cookie_httponly"), " ", ini_get("disable_functions"), " ", ini_get("opcache.force_restart_timeout");' | tail -n1) +[ "$ini_values" = "512M 8M 0 shell_exec 60" ] || fail "PHP_* environment variables did not apply to php.ini. Got: $ini_values" pass "Environment variables apply to php.ini" has_healthcheck=$(docker image inspect --format '{{if .Config.Healthcheck}}yes{{end}}' "$image") @@ -76,7 +79,9 @@ for pair in NGINX_HTTP_PORT:NGINX_WEBROOT APACHE_HTTP_PORT:APACHE_DOCUMENT_ROOT fi done -run_args=(--detach --rm) +# Web images run with OPcache in production mode so the health check and the served page +# cover the FPM and FrankenPHP SAPIs starting with the production defaults. +run_args=(--detach --rm --env PHP_OPCACHE_ENABLE=1) if [ -n "$http_port" ]; then # The container runs unprivileged, so the mounted document root must be world readable. web_dir=$(mktemp -d) diff --git a/src/common/etc/entrypoint.d/0-container-info.sh b/src/common/etc/entrypoint.d/0-container-info.sh index 7ef26e334..379a62fba 100644 --- a/src/common/etc/entrypoint.d/0-container-info.sh +++ b/src/common/etc/entrypoint.d/0-container-info.sh @@ -8,7 +8,9 @@ if [ "$SHOW_WELCOME_MESSAGE" = "false" ] || [ "$DISABLE_DEFAULT_CONFIG" = "true" fi # Get OPcache status -PHP_OPCACHE_STATUS=$(php -r 'echo ini_get("opcache.enable");') +read -r PHP_OPCACHE_STATUS PHP_OPCACHE_VALIDATE_TIMESTAMPS_STATUS < Date: Thu, 10 Sep 2026 21:36:48 +0000 Subject: [PATCH 2/8] Refactor OPcache status retrieval and update production mode notice for clarity --- src/common/etc/entrypoint.d/0-container-info.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/common/etc/entrypoint.d/0-container-info.sh b/src/common/etc/entrypoint.d/0-container-info.sh index 379a62fba..111541796 100644 --- a/src/common/etc/entrypoint.d/0-container-info.sh +++ b/src/common/etc/entrypoint.d/0-container-info.sh @@ -8,9 +8,8 @@ if [ "$SHOW_WELCOME_MESSAGE" = "false" ] || [ "$DISABLE_DEFAULT_CONFIG" = "true" fi # Get OPcache status -read -r PHP_OPCACHE_STATUS PHP_OPCACHE_VALIDATE_TIMESTAMPS_STATUS < Date: Thu, 10 Sep 2026 21:51:53 +0000 Subject: [PATCH 3/8] Update migration and tuning documentation for OPcache improvements --- docs/content/docs/5.guide/5.major-version-migrations.md | 8 ++++---- docs/content/docs/5.guide/6.php-opcache-tuning.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/docs/5.guide/5.major-version-migrations.md b/docs/content/docs/5.guide/5.major-version-migrations.md index 4214b6b36..147b6637d 100644 --- a/docs/content/docs/5.guide/5.major-version-migrations.md +++ b/docs/content/docs/5.guide/5.major-version-migrations.md @@ -26,12 +26,12 @@ If you are on one of these, move to PHP 8.2 or newer on `bookworm`, `trixie`, `a ## Version 4 → Version 5 Migration Version 5 is about OPcache. Setting `PHP_OPCACHE_ENABLE=1` now gives you a production-tuned configuration out of the box instead of PHP's stock defaults. There is one breaking change, and it only affects you if you run with OPcache enabled while your code is mounted as a volume. -If you want to stay on Version 4 while you review the changes, pin your image tag to `v4`: +If you want to stay on Version 4 while you review the changes, pin your image tag to the last v4 release. Version-pinned tags are never rebuilt, so you will not receive security updates until you move to v5. See [how our releases work](/docs/getting-started/upgrade-guide#how-our-releases-work). ```yml [compose.yml] {3} services: php: - image: serversideup/php:8.5-fpm-nginx-v4 + image: serversideup/php:8.5-fpm-nginx-v4.5.1 ``` ### Why we changed OPcache @@ -53,8 +53,8 @@ You are affected if you set `PHP_OPCACHE_ENABLE=1` **and** any of these apply: The fix is one of two things: restart the container after code changes (recommended), or set `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1` to restore the Version 4 behavior. -#### `PHP_OPCACHE_FORCE_RESTART_TIMEOUT` is now applied -This variable existed in Version 4 but never reached `php.ini`. It now works. The default of `180` matches PHP's own default, so nothing changes unless you had set it to something else expecting an effect. +### Fixes +- `PHP_OPCACHE_FORCE_RESTART_TIMEOUT` existed in Version 4 but never reached `php.ini`. It now works. The default of `180` matches PHP's own default, so nothing changes unless you had set it to something else. ### Changed defaults These apply only when `PHP_OPCACHE_ENABLE=1`. They are sized for modern applications and the memory is only used as files are cached. diff --git a/docs/content/docs/5.guide/6.php-opcache-tuning.md b/docs/content/docs/5.guide/6.php-opcache-tuning.md index 810e76b9f..aa40df582 100644 --- a/docs/content/docs/5.guide/6.php-opcache-tuning.md +++ b/docs/content/docs/5.guide/6.php-opcache-tuning.md @@ -57,7 +57,7 @@ The most important of these is `PHP_OPCACHE_VALIDATE_TIMESTAMPS=0`. With it, PHP OPcache reserves one shared memory segment of `PHP_OPCACHE_MEMORY_CONSUMPTION` megabytes when PHP starts. The interned strings buffer and the JIT buffer are carved out of that segment, not added to it. With the defaults, 256 MB is reserved, 16 MB of it holds interned strings, and the remaining 240 MB holds compiled code. -Reserving memory is not the same as using it. The segment is mapped lazily, so the container only pays for pages that are actually written. A typical Laravel application caches somewhere between 60 MB and 150 MB. Every PHP-FPM worker shares the same segment, so the cost does not multiply with the number of workers. +Reserving memory is not the same as using it. The segment is mapped lazily, so the container only pays for pages that are actually written. Every PHP-FPM worker shares the same segment, so the cost does not multiply with the number of workers. ::tip If you are sizing a small container, the number that matters is `PHP_FPM_PM_MAX_CHILDREN` multiplied by `PHP_MEMORY_LIMIT`, not OPcache. Twenty workers with a 256M limit can use far more than OPcache ever will. @@ -114,7 +114,7 @@ Our [Laravel automations](/docs/framework-guides/laravel/automations) run `php a Blade views compiled on first request are also fine. Laravel names each compiled view after a hash of the template path, so a view that is compiled for the first time is a new file that OPcache has never cached. -Queue workers, Horizon, the scheduler, Reverb, and Octane on Swoole or RoadRunner all run in the CLI. They get OPcache because `PHP_OPCACHE_ENABLE` also sets `opcache.enable_cli`. Octane on FrankenPHP uses our `frankenphp` variation, which is not a CLI process and is covered by `opcache.enable` directly. +Queue workers, Horizon, the scheduler, Reverb, and Octane on Swoole or RoadRunner all run in the CLI. They get OPcache because `PHP_OPCACHE_ENABLE` also sets `opcache.enable_cli`. [Octane on FrankenPHP](/docs/framework-guides/laravel/octane) runs the FrankenPHP SAPI, not the CLI, so `opcache.enable` covers it directly. ### WordPress From d596318ae39f5823baa60d1e241415c09d9e0eb9 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 11 Sep 2026 15:44:25 +0000 Subject: [PATCH 4/8] Refactor OPcache documentation: update migration guide and add production performance tuning guide --- .../5.guide/5.major-version-migrations.md | 14 +- .../docs/5.guide/6.php-opcache-tuning.md | 173 ----------------- .../6.production-performance-tuning.md | 175 ++++++++++++++++++ 3 files changed, 183 insertions(+), 179 deletions(-) delete mode 100644 docs/content/docs/5.guide/6.php-opcache-tuning.md create mode 100644 docs/content/docs/5.guide/6.production-performance-tuning.md diff --git a/docs/content/docs/5.guide/5.major-version-migrations.md b/docs/content/docs/5.guide/5.major-version-migrations.md index 147b6637d..3c60b6fb5 100644 --- a/docs/content/docs/5.guide/5.major-version-migrations.md +++ b/docs/content/docs/5.guide/5.major-version-migrations.md @@ -24,7 +24,7 @@ Debian Bullseye and Alpine 3.16 were dropped at the same time. Debian 11 reached If you are on one of these, move to PHP 8.2 or newer on `bookworm`, `trixie`, `alpine3.23`, or `alpine3.24`. See [EOL versions and the legacy-modernization path](https://github.com/serversideup/docker-php/blob/main/SECURITY.md#eol-versions-and-the-legacy-modernization-path). ## Version 4 → Version 5 Migration -Version 5 is about OPcache. Setting `PHP_OPCACHE_ENABLE=1` now gives you a production-tuned configuration out of the box instead of PHP's stock defaults. There is one breaking change, and it only affects you if you run with OPcache enabled while your code is mounted as a volume. +Version 5 is about OPcache. Setting `PHP_OPCACHE_ENABLE=1` now gives you tuned defaults instead of PHP's stock values. There is one breaking change, and it only affects you if you run with OPcache enabled while your code is mounted as a volume. If you want to stay on Version 4 while you review the changes, pin your image tag to the last v4 release. Version-pinned tags are never rebuilt, so you will not receive security updates until you move to v5. See [how our releases work](/docs/getting-started/upgrade-guide#how-our-releases-work). @@ -35,7 +35,7 @@ services: ``` ### Why we changed OPcache -Most people start with these images in development, so OPcache stays off by default to keep your edits showing up instantly. But when you flip it on for production, the settings behind it should be the ones you would have picked yourself after reading the docs. They were not. Version 4 checked every cached file for changes every two seconds, shipped memory sizes that a modern Laravel or Symfony application outgrows, and documented an environment variable that did nothing. Version 5 fixes all of that. [Read the OPcache tuning guide →](/docs/guide/php-opcache-tuning) +Most people start with these images in development, so OPcache stays off by default to keep your edits showing up instantly. But when you flip it on for production, the settings behind it should be the ones you would have picked yourself after reading the docs. They were not. Version 4 checked every cached file for changes every two seconds, shipped PHP's stock memory sizes, and documented an environment variable that did nothing. Version 5 fixes all of that with the values from [Symfony's performance guide](https://symfony.com/doc/current/performance.html#configure-opcache-for-maximum-performance){target="_blank"}, which says "The default OPcache configuration is not suited for Symfony applications." [FrankenPHP's performance guide](https://frankenphp.dev/docs/performance/){target="_blank"} points to the same page "even if you don't use Symfony." [Read the production performance tuning guide →](/docs/guide/production-performance-tuning#php-opcache) ### Breaking changes in Version 5 ::caution @@ -57,17 +57,19 @@ The fix is one of two things: restart the container after code changes (recommen - `PHP_OPCACHE_FORCE_RESTART_TIMEOUT` existed in Version 4 but never reached `php.ini`. It now works. The default of `180` matches PHP's own default, so nothing changes unless you had set it to something else. ### Changed defaults -These apply only when `PHP_OPCACHE_ENABLE=1`. They are sized for modern applications and the memory is only used as files are cached. +The OPcache values apply only when `PHP_OPCACHE_ENABLE=1`, and the memory is only used as files are cached. `PHP_REALPATH_CACHE_TTL` is not an OPcache setting and applies whether OPcache is on or off. It comes from the same [Symfony recommendation](https://symfony.com/doc/current/performance.html#configure-the-php-realpath-cache){target="_blank"}. | Variable | Version 4 | Version 5 | |----------|-----------|-----------| | `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `1` | `0` | | `PHP_OPCACHE_MEMORY_CONSUMPTION` | `128` | `256` | -| `PHP_OPCACHE_INTERNED_STRINGS_BUFFER` | `8` | `16` | -| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `10000` | `20000` | +| `PHP_OPCACHE_INTERNED_STRINGS_BUFFER` | `8` | `32` | +| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `10000` | `32531` | +| `PHP_REALPATH_CACHE_TTL` | `120` | `600` | ### New variables -- `PHP_OPCACHE_PRELOAD` - Path to a preload script. Preloading is one of the biggest wins available for Symfony applications. +- `PHP_OPCACHE_ENABLE_CLI` - Whether CLI commands use OPcache when `PHP_OPCACHE_ENABLE=1`. Defaults to `1`, which is what Version 4 did. Set it to `0` to keep OPcache on for the web server only. +- `PHP_OPCACHE_PRELOAD` - Path to a preload script. Symfony generates one for you and [recommends it](https://symfony.com/doc/current/performance.html#use-the-opcache-class-preloading){target="_blank"}. - `PHP_OPCACHE_PRELOAD_USER` - The user to preload as when the container runs as root. [See the full list of environment variables →](/docs/reference/environment-variable-specification) diff --git a/docs/content/docs/5.guide/6.php-opcache-tuning.md b/docs/content/docs/5.guide/6.php-opcache-tuning.md deleted file mode 100644 index aa40df582..000000000 --- a/docs/content/docs/5.guide/6.php-opcache-tuning.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -head.title: 'PHP OPcache Tuning - Docker PHP - Server Side Up' -description: 'Learn how OPcache works in serversideup/php, what production mode changes, and how to measure and tune it for your application.' -layout: docs -title: PHP OPcache tuning ---- - -::lead-p -OPcache is the single biggest performance win available to a PHP application. It compiles your PHP files once and keeps the result in shared memory, so PHP skips reading and compiling code on every request. This guide explains how our images configure it, what the defaults mean, and how to tune them when your app needs more. -:: - -## Development mode vs production mode - -OPcache is controlled by a single switch, [`PHP_OPCACHE_ENABLE`](/docs/reference/environment-variable-specification). - -| Mode | Setting | What happens | -|------|---------|--------------| -| Development | `PHP_OPCACHE_ENABLE=0` (default) | OPcache is off. Every request reads your files fresh, so edits show up instantly when your code is mounted as a volume. | -| Production | `PHP_OPCACHE_ENABLE=1` | OPcache is on with defaults tuned for containers: files are compiled once and cached until the container restarts. | - -We keep OPcache off by default so nobody loses an afternoon wondering why their change is not showing up. When you build your production image, turn it on: - -```yml [compose.yml] {5} -services: - php: - image: serversideup/php:8.5-fpm-nginx - environment: - PHP_OPCACHE_ENABLE: "1" -``` - -::note -`PHP_OPCACHE_ENABLE` sets both `opcache.enable` and `opcache.enable_cli`, so CLI commands such as `php artisan` also run with OPcache. This helps long-running CLI processes like queue workers and Horizon. -:: - -## What production mode gives you - -When `PHP_OPCACHE_ENABLE=1`, these are the defaults you get: - -| Variable | Default | What it controls | -|----------|---------|------------------| -| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `0` | Whether OPcache checks if a file changed. `0` means files are cached until the container restarts. | -| `PHP_OPCACHE_REVALIDATE_FREQ` | `2` | How many seconds between change checks. Ignored unless timestamps are validated. | -| `PHP_OPCACHE_MEMORY_CONSUMPTION` | `256` | Size of the shared memory segment in megabytes. | -| `PHP_OPCACHE_INTERNED_STRINGS_BUFFER` | `16` | Megabytes reserved inside that segment for interned strings. | -| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `20000` | Maximum number of files that can be cached. | -| `PHP_OPCACHE_FORCE_RESTART_TIMEOUT` | `180` | Seconds to wait for a stuck cache restart before OPcache forces it. | -| `PHP_OPCACHE_SAVE_COMMENTS` | `1` | Keep PHPDoc comments in the cache. Required by Doctrine, PHPUnit, and many Laravel packages. | -| `PHP_OPCACHE_ENABLE_FILE_OVERRIDE` | `0` | Let OPcache answer `file_exists()` from its cache. Left off because it reports deleted files as present when timestamps are not validated. | -| `PHP_OPCACHE_JIT` | `off` | The JIT compiler. See [JIT](#jit) below. | -| `PHP_OPCACHE_JIT_BUFFER_SIZE` | `0` | Memory reserved for JIT code. | -| `PHP_OPCACHE_PRELOAD` | `""` | Path to a preload script. See [Preloading](#preloading) below. | -| `PHP_OPCACHE_PRELOAD_USER` | `""` | User to run the preload script as. Only needed when running as root. | - -The most important of these is `PHP_OPCACHE_VALIDATE_TIMESTAMPS=0`. With it, PHP never touches the filesystem to check whether a cached file changed. This is exactly what you want when your code is built into the image: the files cannot change without a new container anyway, so checking is wasted work. - -## How OPcache uses memory - -OPcache reserves one shared memory segment of `PHP_OPCACHE_MEMORY_CONSUMPTION` megabytes when PHP starts. The interned strings buffer and the JIT buffer are carved out of that segment, not added to it. With the defaults, 256 MB is reserved, 16 MB of it holds interned strings, and the remaining 240 MB holds compiled code. - -Reserving memory is not the same as using it. The segment is mapped lazily, so the container only pays for pages that are actually written. Every PHP-FPM worker shares the same segment, so the cost does not multiply with the number of workers. - -::tip -If you are sizing a small container, the number that matters is `PHP_FPM_PM_MAX_CHILDREN` multiplied by `PHP_MEMORY_LIMIT`, not OPcache. Twenty workers with a 256M limit can use far more than OPcache ever will. -:: - -## Measuring what your app needs - -Guessing is unnecessary. PHP tells you exactly how full the cache is. Drop this into a route or run it through the web server, since the CLI has its own separate cache: - -```php [public/opcache-status.php] - Date: Fri, 11 Sep 2026 15:54:46 +0000 Subject: [PATCH 5/8] Enhance OPcache configuration and documentation: update defaults, improve CLI handling, and clarify production settings --- .../4.these-images-vs-others.md | 4 +-- .../6.default-configurations.md | 2 +- .../4.using-wordpress-with-docker.md | 2 +- .../6.production-performance-tuning.md | 30 ++++++++++++++----- .../1.environment-variable-specification.md | 15 +++++----- scripts/test-image.sh | 12 ++++++-- .../etc/entrypoint.d/0-container-info.sh | 2 +- .../php/conf.d/serversideup-docker-php.ini | 5 ++-- src/variations/cli/Dockerfile | 7 +++-- src/variations/fpm-apache/Dockerfile | 7 +++-- src/variations/fpm-nginx/Dockerfile | 7 +++-- src/variations/fpm/Dockerfile | 7 +++-- src/variations/frankenphp/Dockerfile | 7 +++-- 13 files changed, 67 insertions(+), 40 deletions(-) diff --git a/docs/content/docs/1.getting-started/4.these-images-vs-others.md b/docs/content/docs/1.getting-started/4.these-images-vs-others.md index f0c70f0ce..c309271b4 100644 --- a/docs/content/docs/1.getting-started/4.these-images-vs-others.md +++ b/docs/content/docs/1.getting-started/4.these-images-vs-others.md @@ -77,11 +77,11 @@ We also include additional security hardening: ### Performance Optimized -Every image includes production-tuned defaults based on real-world PHP applications: +Every image ships tuned defaults for PHP, OPcache, and PHP-FPM: **OPcache Configuration** - Pre-configured for optimal memory usage and caching strategy -- Easily toggle between development and production modes +- One variable, `PHP_OPCACHE_ENABLE`, turns OPcache on with tuned defaults - Smart defaults that work for most applications **Process Management** diff --git a/docs/content/docs/1.getting-started/6.default-configurations.md b/docs/content/docs/1.getting-started/6.default-configurations.md index f7f628956..5541c2aee 100644 --- a/docs/content/docs/1.getting-started/6.default-configurations.md +++ b/docs/content/docs/1.getting-started/6.default-configurations.md @@ -84,7 +84,7 @@ The following extensions are installed by default: | **Extension** | **Description** | **Why we included it** | |---------------|-----------------|------------------------| -| [opcache](https://www.php.net/manual/en/book.opcache.php) | The Zend OPcache provides faster PHP execution through opcode caching and optimization. | This is a must-have for PHP performance.

⚠️ OPcache is disabled by default for development. Set [`PHP_OPCACHE_ENABLE=1`](/docs/reference/environment-variable-specification) for production mode with tuned defaults. See the [OPcache tuning guide](/docs/guide/php-opcache-tuning).| +| [opcache](https://www.php.net/manual/en/book.opcache.php) | The Zend OPcache provides faster PHP execution through opcode caching and optimization. | This is a must-have for PHP performance.

⚠️ OPcache is disabled by default so code edits show up right away. Set [`PHP_OPCACHE_ENABLE=1`](/docs/reference/environment-variable-specification) to turn it on with tuned defaults. See the [production performance tuning guide](/docs/guide/production-performance-tuning#php-opcache).| | [mysqli](https://www.php.net/manual/en/book.mysqli.php) | The "MySQL Improved" extension is an older extension for connecting to MySQL 4.1 and above. | **Enabled for fpm-apache only**. This is a legacy MySQL connector required for WordPress.| | [pcntl](https://www.php.net/manual/en/intro.pcntl.php) | Process Control support in PHP implements the Unix style of process creation, program execution, signal handling and process termination. | This is required for [Laravel queues and Laravel Horizon](https://laravel.com/docs/10.x/queues#timeout)| | [pdo_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php) | The MySQL PDO extension allows you to connect to MySQL databases. | MySQL and MariaDB databases are very popular. | diff --git a/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md b/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md index e250cf52e..3995e405b 100644 --- a/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md +++ b/docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md @@ -157,7 +157,7 @@ This approach prioritizes plugin compatibility over modern deployment practices. :: ::warning -With `PHP_OPCACHE_ENABLE=1`, PHP files are cached until the container restarts because [`PHP_OPCACHE_VALIDATE_TIMESTAMPS`](/docs/reference/environment-variable-specification) defaults to `0`. Updates made through the WordPress admin still work because WordPress clears the cache for the files it writes. Changes made with `git pull`, WP-CLI, SFTP, or plugins that write PHP files directly are not picked up until you restart the container. If you deploy this way, either restart the container after each update or set `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1`. [Learn more in the OPcache tuning guide →](/docs/guide/php-opcache-tuning) +With `PHP_OPCACHE_ENABLE=1`, PHP files are cached until the container restarts because [`PHP_OPCACHE_VALIDATE_TIMESTAMPS`](/docs/reference/environment-variable-specification) defaults to `0`. Updates made through the WordPress admin still work because WordPress clears the cache for the files it writes with [`wp_opcache_invalidate()`](https://developer.wordpress.org/reference/functions/wp_opcache_invalidate/){target="_blank"}. Changes made with `git pull`, WP-CLI, SFTP, or plugins that write PHP files directly are not picked up until you restart the container. If you deploy this way, either restart the container after each update or set `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1`. [Learn more in the production performance tuning guide →](/docs/guide/production-performance-tuning#php-opcache) :: ### Which approach should you choose? diff --git a/docs/content/docs/5.guide/6.production-performance-tuning.md b/docs/content/docs/5.guide/6.production-performance-tuning.md index 0b783ce11..d160a3398 100644 --- a/docs/content/docs/5.guide/6.production-performance-tuning.md +++ b/docs/content/docs/5.guide/6.production-performance-tuning.md @@ -70,15 +70,29 @@ PHP reports how full the cache is. Put this file in your public directory and op ```php [public/opcache-status.php] round($bytes / 1048576) . ' MB'; +$files = "{$stats['num_cached_keys']} of {$stats['max_cached_keys']}"; + +$report = [ + 'Cache full' => $status['cache_full'] ? 'yes' : 'no', + 'Cached files' => $files, + 'Memory used' => $toMegabytes($memory['used_memory']), + 'Memory free' => $toMegabytes($memory['free_memory']), + 'Interned strings free' => $toMegabytes($strings['free_memory']), + 'Out of memory restarts' => $stats['oom_restarts'], + 'Hash restarts' => $stats['hash_restarts'], +]; + +foreach ($report as $label => $value) { + echo "$label: $value +"; +} ``` ::caution diff --git a/docs/content/docs/8.reference/1.environment-variable-specification.md b/docs/content/docs/8.reference/1.environment-variable-specification.md index 9b55548dc..00abcfdf0 100644 --- a/docs/content/docs/8.reference/1.environment-variable-specification.md +++ b/docs/content/docs/8.reference/1.environment-variable-specification.md @@ -100,23 +100,24 @@ Setting environment variables all depends on what method you're using to run you `PHP_MAX_INPUT_TIME`
*Default: "-1"*|This sets the maximum time in seconds a script is allowed to parse input data, like POST and GET. Timing begins at the moment PHP is invoked at the server and ends when execution begins. The default setting is -1, which means that max_execution_time is used instead. Set to 0 to allow unlimited time. This directive is hardcoded to -1 for the CLI SAPI by PHP. (Official docs)|all `PHP_MAX_INPUT_VARS`
*Default: "1000"*|Set the limits for number of input variables (e.g., POST, GET, or COOKIE variables) that PHP will process in a single request. (Official docs)|all `PHP_MEMORY_LIMIT`
*Default: "256M"*|Set the maximum amount of memory in bytes that a script is allowed to allocate. (Official docs)|all -`PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. `0` is development mode and `1` is production mode with the tuned defaults below. ⚠️ This will set **both values** for `opcache.enable` and `opcache.enable_cli`, so CLI commands like `php artisan` use OPcache too. See the [OPcache tuning guide](/docs/guide/php-opcache-tuning). (Official docs)|all +`PHP_OPCACHE_ENABLE`
*Default: "0" (to keep developers sane)*|Enable or disable OPcache. `1` also applies the tuned `PHP_OPCACHE_*` defaults below. CLI commands like `php artisan` also use OPcache when `PHP_OPCACHE_ENABLE_CLI=1`. See the [production performance tuning guide](/docs/guide/production-performance-tuning#php-opcache). (Official docs)|all +`PHP_OPCACHE_ENABLE_CLI`
*Default: "1"*|Enable or disable OPcache for CLI commands like `php artisan`. Only takes effect when `PHP_OPCACHE_ENABLE=1`. Set to `0` to keep OPcache on for the web server only, for example so `PHP_OPCACHE_PRELOAD` does not run on every CLI command. (Official docs)|all `PHP_OPCACHE_ENABLE_FILE_OVERRIDE`
*Default: "0"*|Enable or disable file existence override (file_exists, etc.). (Official docs)|all `PHP_OPCACHE_FORCE_RESTART_TIMEOUT`
*Default: "180"*|The number of seconds to wait for a scheduled restart to begin if the cache isn't active, in seconds. If the timeout is hit, then OPcache assumes that something is wrong and will kill the processes holding locks on the cache to permit a restart. (Official docs)|all -`PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "16"*|The amount of memory used to store interned strings, in megabytes. This is reserved inside `PHP_OPCACHE_MEMORY_CONSUMPTION`, not in addition to it. (Official docs)|all +`PHP_OPCACHE_INTERNED_STRINGS_BUFFER`
*Default: "32"*|The amount of memory used to store interned strings, in megabytes. This is reserved inside `PHP_OPCACHE_MEMORY_CONSUMPTION`, not in addition to it. (Official docs)|all `PHP_OPCACHE_JIT`
*Default: "off"*|Enable or disable the JIT compiler. To turn it on, set this to `tracing` **and** set `PHP_OPCACHE_JIT_BUFFER_SIZE` to a non-zero value like `64M`. (Official docs)|all -`PHP_OPCACHE_JIT_BUFFER_SIZE`
*Default: "0"*|The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. This is reserved inside `PHP_OPCACHE_MEMORY_CONSUMPTION`. (Official docs)|all -`PHP_OPCACHE_MAX_ACCELERATED_FILES`
*Default: "20000"*|The maximum number of keys (scripts) in the OPcache hash table. (Official docs)|all +`PHP_OPCACHE_JIT_BUFFER_SIZE`
*Default: "0"*|The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT. This is added on top of `PHP_OPCACHE_MEMORY_CONSUMPTION`, so the shared memory segment becomes the sum of both. (Official docs)|all +`PHP_OPCACHE_MAX_ACCELERATED_FILES`
*Default: "32531"*|The maximum number of keys (scripts) in the OPcache hash table. PHP rounds this up to the next value in its prime number table (16229, 32531, 65407, and so on), so `32531` is the exact value PHP uses and reports in `opcache_get_status()`. (Official docs)|all `PHP_OPCACHE_MEMORY_CONSUMPTION`
*Default: "256"*|The amount of shared memory reserved for OPcache, in megabytes. Memory is only used as files are cached, so a larger value costs nothing until it is needed. (Official docs)|all -`PHP_OPCACHE_PRELOAD`
*Default: ""*|Path to a PHP script that OPcache compiles and runs at startup (preloading). Empty disables preloading. ⚠️ Since `PHP_OPCACHE_ENABLE` also enables OPcache for the CLI, the script runs on every `php` command too. Preloading as root requires `PHP_OPCACHE_PRELOAD_USER`. See the [OPcache tuning guide](/docs/guide/php-opcache-tuning). (Official docs)|all +`PHP_OPCACHE_PRELOAD`
*Default: ""*|Path to a PHP script that OPcache compiles and runs at startup (preloading). Empty disables preloading. ⚠️ The script also runs on every `php` command unless `PHP_OPCACHE_ENABLE_CLI=0`. Preloading as root requires `PHP_OPCACHE_PRELOAD_USER`. See the [production performance tuning guide](/docs/guide/production-performance-tuning#php-opcache). (Official docs)|all `PHP_OPCACHE_PRELOAD_USER`
*Default: ""*|The system user to run the preload script as. Only needed when the web server runs as root, since PHP refuses to preload as root without it. (Official docs)|all `PHP_OPCACHE_REVALIDATE_FREQ`
*Default: "2"*|How often the OPcache checks for updates to cached files (in seconds). Only applies when `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1`. (Official docs)|all -`PHP_OPCACHE_SAVE_COMMENTS`
*Default: "1"*|Keep PHPDoc comments in the cached code. Setting this to `0` saves a little memory but breaks any code that reads PHPDoc annotations at runtime (Doctrine, PHPUnit, and many Laravel packages). (Official docs)|all +`PHP_OPCACHE_SAVE_COMMENTS`
*Default: "1"*|Keep PHPDoc comments in the cached code. Setting this to `0` saves a little memory but breaks any code that reads PHPDoc annotations at runtime (Doctrine, Zend Framework 2, and PHPUnit). (Official docs)|all `PHP_OPCACHE_VALIDATE_TIMESTAMPS`
*Default: "0"*|Whether OPcache checks for changes to files. With `0`, PHP files are cached until the container restarts, which is ideal for immutable containers. Set to `1` if you mount your code as a volume and still want OPcache enabled. (Official docs)|all `PHP_OPEN_BASEDIR`
*Default: "None"* |Limit the files that can be accessed by PHP to the specified directory-tree, including the file itself. `open_basedir` is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed. (Official docs)| all `PHP_POST_MAX_SIZE`
*Default: "100M"*|Sets max size of post data allowed. (Official docs)|all `PHP_REALPATH_CACHE_SIZE`
*Default: "4096K"*|Size of the realpath cache. Applications with many files (large `vendor/` directories) may benefit from a larger cache. Note: the cache is disabled when `PHP_OPEN_BASEDIR` is set. (Official docs)|all -`PHP_REALPATH_CACHE_TTL`
*Default: "120"*|The duration of time, in seconds for which to cache realpath information for a given file or directory. (Official docs)|all +`PHP_REALPATH_CACHE_TTL`
*Default: "600"*|The duration of time, in seconds for which to cache realpath information for a given file or directory. `600` is the value Symfony's performance guide recommends for applications that open many PHP files. (Symfony docs) (Official docs)|all `PHP_SESSION_COOKIE_HTTPONLY`
*Default: "On"*|Add the `HttpOnly` flag to the session cookie so browser scripts cannot read it. On by default as recommended by PHP. Only applies to native PHP sessions. Laravel manages its own session cookie flags. (Official docs)|all `PHP_SESSION_COOKIE_SECURE`
*Default: "false"*|Specifies whether the session cookie should only be sent over HTTPS. Off by default so local development over HTTP works. Set to `true` in production when serving over HTTPS. Only applies to native PHP sessions. Laravel manages its own session cookie flags. (Official docs)|all `PHP_UPLOAD_MAX_FILE_SIZE`
*Default: "100M"*|The maximum size of an uploaded file. (Official docs)|all diff --git a/scripts/test-image.sh b/scripts/test-image.sh index cea5ccec1..0aab5bdf0 100755 --- a/scripts/test-image.sh +++ b/scripts/test-image.sh @@ -48,7 +48,7 @@ pass "Extensions loaded: $expected_extensions" # PHP_* environment variables reach php.ini through ${VAR} substitution. Override a few # of the different value types (size, boolean, list) and confirm PHP sees them. -# OPcache is enabled so the CLI SAPI also allocates the shared cache with the production defaults. +# OPcache is enabled so the CLI SAPI also allocates the shared cache with the tuned defaults. ini_values=$(docker run --rm \ --env PHP_MEMORY_LIMIT=512M \ --env PHP_REALPATH_CACHE_SIZE=8M \ @@ -60,6 +60,12 @@ ini_values=$(docker run --rm \ [ "$ini_values" = "512M 8M 0 shell_exec 60" ] || fail "PHP_* environment variables did not apply to php.ini. Got: $ini_values" pass "Environment variables apply to php.ini" +# PHP_OPCACHE_ENABLE_CLI turns OPcache off for the CLI SAPI while opcache.enable stays on for the web server. +cli_opcache=$(docker run --rm --env PHP_OPCACHE_ENABLE=1 --env PHP_OPCACHE_ENABLE_CLI=0 \ + "$image" php -r 'echo function_exists("opcache_get_status") && opcache_get_status(false) !== false ? "on" : "off";' | tail -n1) +[ "$cli_opcache" = "off" ] || fail "PHP_OPCACHE_ENABLE_CLI=0 did not disable OPcache for the CLI. Got: $cli_opcache" +pass "PHP_OPCACHE_ENABLE_CLI disables the CLI cache" + has_healthcheck=$(docker image inspect --format '{{if .Config.Healthcheck}}yes{{end}}' "$image") if [ -z "$has_healthcheck" ]; then pass "No HEALTHCHECK defined, skipping startup check" @@ -79,8 +85,8 @@ for pair in NGINX_HTTP_PORT:NGINX_WEBROOT APACHE_HTTP_PORT:APACHE_DOCUMENT_ROOT fi done -# Web images run with OPcache in production mode so the health check and the served page -# cover the FPM and FrankenPHP SAPIs starting with the production defaults. +# Web images run with OPcache enabled so the health check and the served page +# cover the FPM and FrankenPHP SAPIs starting with the tuned defaults. run_args=(--detach --rm --env PHP_OPCACHE_ENABLE=1) if [ -n "$http_port" ]; then # The container runs unprivileged, so the mounted document root must be world readable. diff --git a/src/common/etc/entrypoint.d/0-container-info.sh b/src/common/etc/entrypoint.d/0-container-info.sh index 111541796..7770717b7 100644 --- a/src/common/etc/entrypoint.d/0-container-info.sh +++ b/src/common/etc/entrypoint.d/0-container-info.sh @@ -63,5 +63,5 @@ Brought to you by serversideup.net if [ "$PHP_OPCACHE_STATUS" = "0" ]; then echo "👉 [NOTICE]: Improve PHP performance by setting PHP_OPCACHE_ENABLE=1 (recommended for production)." elif [ "$PHP_OPCACHE_VALIDATE_TIMESTAMPS_STATUS" = "0" ]; then - echo "👉 [NOTICE]: OPcache is in production mode. Code changes require a container restart. Learn more: https://serversideup.net/open-source/docker-php/docs/guide/php-opcache-tuning" + echo "👉 [NOTICE]: OPcache is enabled and PHP_OPCACHE_VALIDATE_TIMESTAMPS=0. Code changes require a container restart. Learn more: https://serversideup.net/open-source/docker-php/docs/guide/production-performance-tuning#php-opcache" fi \ No newline at end of file diff --git a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini index a459bede7..e8b443c36 100644 --- a/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini +++ b/src/common/usr/local/etc/php/conf.d/serversideup-docker-php.ini @@ -1791,8 +1791,9 @@ ldap.max_links = -1 ; Determines if Zend OPCache is enabled opcache.enable=${PHP_OPCACHE_ENABLE} -; Determines if Zend OPCache is enabled for the CLI version of PHP -opcache.enable_cli=${PHP_OPCACHE_ENABLE} +; Determines if Zend OPCache is enabled for the CLI version of PHP. +; Only takes effect when opcache.enable is also on. +opcache.enable_cli=${PHP_OPCACHE_ENABLE_CLI} ; The OPcache shared memory storage size. opcache.memory_consumption=${PHP_OPCACHE_MEMORY_CONSUMPTION} diff --git a/src/variations/cli/Dockerfile b/src/variations/cli/Dockerfile index b0d26d3be..f29b7ab1d 100644 --- a/src/variations/cli/Dockerfile +++ b/src/variations/cli/Dockerfile @@ -43,12 +43,13 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_CLI="1" \ PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ - PHP_OPCACHE_INTERNED_STRINGS_BUFFER="16" \ + PHP_OPCACHE_INTERNED_STRINGS_BUFFER="32" \ PHP_OPCACHE_JIT="off" \ PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ - PHP_OPCACHE_MAX_ACCELERATED_FILES="20000" \ + PHP_OPCACHE_MAX_ACCELERATED_FILES="32531" \ PHP_OPCACHE_MEMORY_CONSUMPTION="256" \ PHP_OPCACHE_PRELOAD="" \ PHP_OPCACHE_PRELOAD_USER="" \ @@ -58,7 +59,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_REALPATH_CACHE_SIZE="4096K" \ - PHP_REALPATH_CACHE_TTL="120" \ + PHP_REALPATH_CACHE_TTL="600" \ PHP_SESSION_COOKIE_HTTPONLY="On" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ diff --git a/src/variations/fpm-apache/Dockerfile b/src/variations/fpm-apache/Dockerfile index 1f9ea811a..04e10ce54 100644 --- a/src/variations/fpm-apache/Dockerfile +++ b/src/variations/fpm-apache/Dockerfile @@ -80,12 +80,13 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_CLI="1" \ PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ - PHP_OPCACHE_INTERNED_STRINGS_BUFFER="16" \ + PHP_OPCACHE_INTERNED_STRINGS_BUFFER="32" \ PHP_OPCACHE_JIT="off" \ PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ - PHP_OPCACHE_MAX_ACCELERATED_FILES="20000" \ + PHP_OPCACHE_MAX_ACCELERATED_FILES="32531" \ PHP_OPCACHE_MEMORY_CONSUMPTION="256" \ PHP_OPCACHE_PRELOAD="" \ PHP_OPCACHE_PRELOAD_USER="" \ @@ -95,7 +96,7 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_REALPATH_CACHE_SIZE="4096K" \ - PHP_REALPATH_CACHE_TTL="120" \ + PHP_REALPATH_CACHE_TTL="600" \ PHP_SESSION_COOKIE_HTTPONLY="On" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ diff --git a/src/variations/fpm-nginx/Dockerfile b/src/variations/fpm-nginx/Dockerfile index e3c472a49..e8d12a90d 100644 --- a/src/variations/fpm-nginx/Dockerfile +++ b/src/variations/fpm-nginx/Dockerfile @@ -154,12 +154,13 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_CLI="1" \ PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ - PHP_OPCACHE_INTERNED_STRINGS_BUFFER="16" \ + PHP_OPCACHE_INTERNED_STRINGS_BUFFER="32" \ PHP_OPCACHE_JIT="off" \ PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ - PHP_OPCACHE_MAX_ACCELERATED_FILES="20000" \ + PHP_OPCACHE_MAX_ACCELERATED_FILES="32531" \ PHP_OPCACHE_MEMORY_CONSUMPTION="256" \ PHP_OPCACHE_PRELOAD="" \ PHP_OPCACHE_PRELOAD_USER="" \ @@ -169,7 +170,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_REALPATH_CACHE_SIZE="4096K" \ - PHP_REALPATH_CACHE_TTL="120" \ + PHP_REALPATH_CACHE_TTL="600" \ PHP_SESSION_COOKIE_HTTPONLY="On" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ diff --git a/src/variations/fpm/Dockerfile b/src/variations/fpm/Dockerfile index 05d034f75..eeead1f5e 100644 --- a/src/variations/fpm/Dockerfile +++ b/src/variations/fpm/Dockerfile @@ -52,12 +52,13 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_CLI="1" \ PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ - PHP_OPCACHE_INTERNED_STRINGS_BUFFER="16" \ + PHP_OPCACHE_INTERNED_STRINGS_BUFFER="32" \ PHP_OPCACHE_JIT="off" \ PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ - PHP_OPCACHE_MAX_ACCELERATED_FILES="20000" \ + PHP_OPCACHE_MAX_ACCELERATED_FILES="32531" \ PHP_OPCACHE_MEMORY_CONSUMPTION="256" \ PHP_OPCACHE_PRELOAD="" \ PHP_OPCACHE_PRELOAD_USER="" \ @@ -67,7 +68,7 @@ ENV APP_BASE_DIR=/var/www/html \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_REALPATH_CACHE_SIZE="4096K" \ - PHP_REALPATH_CACHE_TTL="120" \ + PHP_REALPATH_CACHE_TTL="600" \ PHP_SESSION_COOKIE_HTTPONLY="On" \ PHP_SESSION_COOKIE_SECURE=Off \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 1bc277270..2ec91e048 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -163,12 +163,13 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ PHP_MAX_INPUT_VARS="1000" \ PHP_MEMORY_LIMIT="256M" \ PHP_OPCACHE_ENABLE="0" \ + PHP_OPCACHE_ENABLE_CLI="1" \ PHP_OPCACHE_ENABLE_FILE_OVERRIDE="0" \ PHP_OPCACHE_FORCE_RESTART_TIMEOUT="180" \ - PHP_OPCACHE_INTERNED_STRINGS_BUFFER="16" \ + PHP_OPCACHE_INTERNED_STRINGS_BUFFER="32" \ PHP_OPCACHE_JIT="off" \ PHP_OPCACHE_JIT_BUFFER_SIZE="0" \ - PHP_OPCACHE_MAX_ACCELERATED_FILES="20000" \ + PHP_OPCACHE_MAX_ACCELERATED_FILES="32531" \ PHP_OPCACHE_MEMORY_CONSUMPTION="256" \ PHP_OPCACHE_PRELOAD="" \ PHP_OPCACHE_PRELOAD_USER="" \ @@ -178,7 +179,7 @@ LABEL org.opencontainers.image.title="serversideup/php (frankenphp)" \ PHP_OPEN_BASEDIR="" \ PHP_POST_MAX_SIZE="100M" \ PHP_REALPATH_CACHE_SIZE="4096K" \ - PHP_REALPATH_CACHE_TTL="120" \ + PHP_REALPATH_CACHE_TTL="600" \ PHP_SESSION_COOKIE_HTTPONLY="On" \ PHP_SESSION_COOKIE_SECURE=false \ PHP_UPLOAD_MAX_FILE_SIZE="100M" \ From 87c97f2fa9ac23c38db05d5e7e0c54b8ff67ae5e Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 11 Sep 2026 17:34:08 +0000 Subject: [PATCH 6/8] Update OPcache notice link for performance tuning documentation --- src/common/etc/entrypoint.d/0-container-info.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/etc/entrypoint.d/0-container-info.sh b/src/common/etc/entrypoint.d/0-container-info.sh index 7770717b7..bf8124eaa 100644 --- a/src/common/etc/entrypoint.d/0-container-info.sh +++ b/src/common/etc/entrypoint.d/0-container-info.sh @@ -63,5 +63,5 @@ Brought to you by serversideup.net if [ "$PHP_OPCACHE_STATUS" = "0" ]; then echo "👉 [NOTICE]: Improve PHP performance by setting PHP_OPCACHE_ENABLE=1 (recommended for production)." elif [ "$PHP_OPCACHE_VALIDATE_TIMESTAMPS_STATUS" = "0" ]; then - echo "👉 [NOTICE]: OPcache is enabled and PHP_OPCACHE_VALIDATE_TIMESTAMPS=0. Code changes require a container restart. Learn more: https://serversideup.net/open-source/docker-php/docs/guide/production-performance-tuning#php-opcache" + echo "👉 [NOTICE]: OPcache is enabled and PHP_OPCACHE_VALIDATE_TIMESTAMPS=0. Code changes require a container restart. Learn more: https://serversideup.net/docker-php/performance/" fi \ No newline at end of file From 788a0d616b24109b440fdc7ee5b2f61450dea19c Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 11 Sep 2026 17:36:00 +0000 Subject: [PATCH 7/8] Refine OPcache performance tuning guide: remove placeholder text and clarify custom ini file options --- docs/content/docs/5.guide/6.production-performance-tuning.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/5.guide/6.production-performance-tuning.md b/docs/content/docs/5.guide/6.production-performance-tuning.md index d160a3398..027017e44 100644 --- a/docs/content/docs/5.guide/6.production-performance-tuning.md +++ b/docs/content/docs/5.guide/6.production-performance-tuning.md @@ -6,7 +6,7 @@ title: Production performance tuning --- ::lead-p -This guide covers the settings that matter for performance once your application is in production, where our defaults come from, and how to tune them when your app needs more. It starts with OPcache. Other areas will be added over time. +This guide covers the settings that matter for performance once your application is in production, where our defaults come from, and how to tune them when your app needs more. :: ## PHP OPcache @@ -175,7 +175,7 @@ Measure before and after. If your response times do not move, leave it off. ### Advanced settings -Anything OPcache supports that is not an environment variable can be set through a [custom ini file](/docs/customizing-the-image/changing-common-php-settings). Two that occasionally matter: +If there are options that you don't see in the environment variables, you can set them through a [custom ini file](/docs/customizing-the-image/changing-common-php-settings). For example, in some ocasions you may want to set: - `opcache.huge_code_pages=1` copies compiled code into huge pages. It "requires appropriate OS configuration" ([PHP manual](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.huge_code_pages){target="_blank"}). - `opcache.file_cache=/path` adds a second-level cache on disk that helps "at server restart or SHM reset" ([PHP manual](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.file-cache){target="_blank"}). From 84fe9fa36aab89c450ac7a377cc396c554a0889f Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 11 Sep 2026 17:43:42 +0000 Subject: [PATCH 8/8] Clarify OPcache explanation in production performance tuning guide --- docs/content/docs/5.guide/6.production-performance-tuning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/5.guide/6.production-performance-tuning.md b/docs/content/docs/5.guide/6.production-performance-tuning.md index 027017e44..1aae13fea 100644 --- a/docs/content/docs/5.guide/6.production-performance-tuning.md +++ b/docs/content/docs/5.guide/6.production-performance-tuning.md @@ -11,7 +11,7 @@ This guide covers the settings that matter for performance once your application ## PHP OPcache -OPcache "improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request" ([PHP manual](https://www.php.net/manual/en/book.opcache.php){target="_blank"}). It is the first thing to get right. +OPcache "improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request" ([PHP manual](https://www.php.net/manual/en/book.opcache.php){target="_blank"}). This means that every request skips the work of reading and parsing your PHP files, so it is faster. It also means that changes to your code are not seen until the cache is cleared, which is why we keep it off by default. ### Turning it on