Skip to content

Commit ff1fda2

Browse files
committed
Enhance OPcache configuration and documentation: update defaults, improve CLI handling, and clarify production settings
1 parent d596318 commit ff1fda2

13 files changed

Lines changed: 67 additions & 40 deletions

File tree

‎docs/content/docs/1.getting-started/4.these-images-vs-others.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ We also include additional security hardening:
7777

7878
### Performance Optimized
7979

80-
Every image includes production-tuned defaults based on real-world PHP applications:
80+
Every image ships tuned defaults for PHP, OPcache, and PHP-FPM:
8181

8282
**OPcache Configuration**
8383
- Pre-configured for optimal memory usage and caching strategy
84-
- Easily toggle between development and production modes
84+
- One variable, `PHP_OPCACHE_ENABLE`, turns OPcache on with tuned defaults
8585
- Smart defaults that work for most applications
8686

8787
**Process Management**

‎docs/content/docs/1.getting-started/6.default-configurations.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ The following extensions are installed by default:
8484

8585
| **Extension** | **Description** | **Why we included it** |
8686
|---------------|-----------------|------------------------|
87-
| [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.<br /><br />⚠️ 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).|
87+
| [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.<br /><br />⚠️ 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).|
8888
| [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.|
8989
| [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)|
9090
| [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. |

‎docs/content/docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ This approach prioritizes plugin compatibility over modern deployment practices.
157157
::
158158

159159
::warning
160-
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)
160+
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)
161161
::
162162

163163
### Which approach should you choose?

‎docs/content/docs/5.guide/6.production-performance-tuning.md‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,29 @@ PHP reports how full the cache is. Put this file in your public directory and op
7070

7171
```php [public/opcache-status.php]
7272
<?php
73-
$status = opcache_get_status(false);
73+
header('Content-Type: text/plain');
7474
75-
echo 'Cache full: ' . ($status['cache_full'] ? 'yes' : 'no') . PHP_EOL;
76-
echo 'Cached files: ' . $status['opcache_statistics']['num_cached_keys'] . ' of ' . $status['opcache_statistics']['max_cached_keys'] . PHP_EOL;
77-
echo 'Memory used: ' . round($status['memory_usage']['used_memory'] / 1048576) . ' MB' . PHP_EOL;
78-
echo 'Memory free: ' . round($status['memory_usage']['free_memory'] / 1048576) . ' MB' . PHP_EOL;
79-
echo 'Interned strings free: ' . round($status['interned_strings_usage']['free_memory'] / 1048576) . ' MB' . PHP_EOL;
80-
echo 'Out of memory restarts: ' . $status['opcache_statistics']['oom_restarts'] . PHP_EOL;
81-
echo 'Hash restarts: ' . $status['opcache_statistics']['hash_restarts'] . PHP_EOL;
75+
$status = opcache_get_status(false);
76+
$stats = $status['opcache_statistics'];
77+
$memory = $status['memory_usage'];
78+
$strings = $status['interned_strings_usage'];
79+
$toMegabytes = fn (int $bytes) => round($bytes / 1048576) . ' MB';
80+
$files = "{$stats['num_cached_keys']} of {$stats['max_cached_keys']}";
81+
82+
$report = [
83+
'Cache full' => $status['cache_full'] ? 'yes' : 'no',
84+
'Cached files' => $files,
85+
'Memory used' => $toMegabytes($memory['used_memory']),
86+
'Memory free' => $toMegabytes($memory['free_memory']),
87+
'Interned strings free' => $toMegabytes($strings['free_memory']),
88+
'Out of memory restarts' => $stats['oom_restarts'],
89+
'Hash restarts' => $stats['hash_restarts'],
90+
];
91+
92+
foreach ($report as $label => $value) {
93+
echo "$label: $value
94+
";
95+
}
8296
```
8397

8498
::caution

‎docs/content/docs/8.reference/1.environment-variable-specification.md‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,24 @@ Setting environment variables all depends on what method you're using to run you
100100
`PHP_MAX_INPUT_TIME`<br />*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. (<a target="_blank" href="https://php.net/max-input-time">Official docs</a>)|all
101101
`PHP_MAX_INPUT_VARS`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/info.configuration.php#ini.max-input-vars">Official docs</a>)|all
102102
`PHP_MEMORY_LIMIT`<br />*Default: "256M"*|Set the maximum amount of memory in bytes that a script is allowed to allocate. (<a target="_blank" href="https://www.php.net/manual/en/ini.core.php#ini.memory-limit">Official docs</a>)|all
103-
`PHP_OPCACHE_ENABLE`<br />*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). (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable">Official docs</a>)|all
103+
`PHP_OPCACHE_ENABLE`<br />*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). (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable">Official docs</a>)|all
104+
`PHP_OPCACHE_ENABLE_CLI`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable-cli">Official docs</a>)|all
104105
`PHP_OPCACHE_ENABLE_FILE_OVERRIDE`<br />*Default: "0"*|Enable or disable file existence override (file_exists, etc.). (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable-file-override">Official docs</a>)|all
105106
`PHP_OPCACHE_FORCE_RESTART_TIMEOUT`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.force-restart-timeout">Official docs</a>)|all
106-
`PHP_OPCACHE_INTERNED_STRINGS_BUFFER`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.interned-strings-buffer">Official docs</a>)|all
107+
`PHP_OPCACHE_INTERNED_STRINGS_BUFFER`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.interned-strings-buffer">Official docs</a>)|all
107108
`PHP_OPCACHE_JIT`<br />*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`. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit">Official docs</a>)|all
108-
`PHP_OPCACHE_JIT_BUFFER_SIZE`<br />*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`. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit-buffer-size">Official docs</a>)|all
109-
`PHP_OPCACHE_MAX_ACCELERATED_FILES`<br />*Default: "20000"*|The maximum number of keys (scripts) in the OPcache hash table. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.max-accelerated-files">Official docs</a>)|all
109+
`PHP_OPCACHE_JIT_BUFFER_SIZE`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit-buffer-size">Official docs</a>)|all
110+
`PHP_OPCACHE_MAX_ACCELERATED_FILES`<br />*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()`. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.max-accelerated-files">Official docs</a>)|all
110111
`PHP_OPCACHE_MEMORY_CONSUMPTION`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.memory-consumption">Official docs</a>)|all
111-
`PHP_OPCACHE_PRELOAD`<br />*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). (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.preload">Official docs</a>)|all
112+
`PHP_OPCACHE_PRELOAD`<br />*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). (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.preload">Official docs</a>)|all
112113
`PHP_OPCACHE_PRELOAD_USER`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.preload-user">Official docs</a>)|all
113114
`PHP_OPCACHE_REVALIDATE_FREQ`<br />*Default: "2"*|How often the OPcache checks for updates to cached files (in seconds). Only applies when `PHP_OPCACHE_VALIDATE_TIMESTAMPS=1`. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.revalidate-freq">Official docs</a>)|all
114-
`PHP_OPCACHE_SAVE_COMMENTS`<br />*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). (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments">Official docs</a>)|all
115+
`PHP_OPCACHE_SAVE_COMMENTS`<br />*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). (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.save-comments">Official docs</a>)|all
115116
`PHP_OPCACHE_VALIDATE_TIMESTAMPS`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.validate-timestamps">Official docs</a>)|all
116117
`PHP_OPEN_BASEDIR`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/ini.core.php#ini.open-basedir">Official docs</a>)| all
117118
`PHP_POST_MAX_SIZE`<br />*Default: "100M"*|Sets max size of post data allowed. (<a target="_blank" href="https://www.php.net/manual/en/ini.core.php#ini.post-max-size">Official docs</a>)|all
118119
`PHP_REALPATH_CACHE_SIZE`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/ini.core.php#ini.realpath-cache-size">Official docs</a>)|all
119-
`PHP_REALPATH_CACHE_TTL`<br />*Default: "120"*|The duration of time, in seconds for which to cache realpath information for a given file or directory. (<a target="_blank" href="https://www.php.net/manual/en/ini.core.php#ini.realpath-cache-ttl">Official docs</a>)|all
120+
`PHP_REALPATH_CACHE_TTL`<br />*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. (<a target="_blank" href="https://symfony.com/doc/current/performance.html#configure-the-php-realpath-cache">Symfony docs</a>) (<a target="_blank" href="https://www.php.net/manual/en/ini.core.php#ini.realpath-cache-ttl">Official docs</a>)|all
120121
`PHP_SESSION_COOKIE_HTTPONLY`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-httponly">Official docs</a>)|all
121122
`PHP_SESSION_COOKIE_SECURE`<br />*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. (<a target="_blank" href="https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-secure">Official docs</a>)|all
122123
`PHP_UPLOAD_MAX_FILE_SIZE`<br />*Default: "100M"*|The maximum size of an uploaded file. (<a target="_blank" href="https://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize">Official docs</a>)|all

‎scripts/test-image.sh‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pass "Extensions loaded: $expected_extensions"
4848

4949
# PHP_* environment variables reach php.ini through ${VAR} substitution. Override a few
5050
# of the different value types (size, boolean, list) and confirm PHP sees them.
51-
# OPcache is enabled so the CLI SAPI also allocates the shared cache with the production defaults.
51+
# OPcache is enabled so the CLI SAPI also allocates the shared cache with the tuned defaults.
5252
ini_values=$(docker run --rm \
5353
--env PHP_MEMORY_LIMIT=512M \
5454
--env PHP_REALPATH_CACHE_SIZE=8M \
@@ -60,6 +60,12 @@ ini_values=$(docker run --rm \
6060
[ "$ini_values" = "512M 8M 0 shell_exec 60" ] || fail "PHP_* environment variables did not apply to php.ini. Got: $ini_values"
6161
pass "Environment variables apply to php.ini"
6262

63+
# PHP_OPCACHE_ENABLE_CLI turns OPcache off for the CLI SAPI while opcache.enable stays on for the web server.
64+
cli_opcache=$(docker run --rm --env PHP_OPCACHE_ENABLE=1 --env PHP_OPCACHE_ENABLE_CLI=0 \
65+
"$image" php -r 'echo function_exists("opcache_get_status") && opcache_get_status(false) !== false ? "on" : "off";' | tail -n1)
66+
[ "$cli_opcache" = "off" ] || fail "PHP_OPCACHE_ENABLE_CLI=0 did not disable OPcache for the CLI. Got: $cli_opcache"
67+
pass "PHP_OPCACHE_ENABLE_CLI disables the CLI cache"
68+
6369
has_healthcheck=$(docker image inspect --format '{{if .Config.Healthcheck}}yes{{end}}' "$image")
6470
if [ -z "$has_healthcheck" ]; then
6571
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
7985
fi
8086
done
8187

82-
# Web images run with OPcache in production mode so the health check and the served page
83-
# cover the FPM and FrankenPHP SAPIs starting with the production defaults.
88+
# Web images run with OPcache enabled so the health check and the served page
89+
# cover the FPM and FrankenPHP SAPIs starting with the tuned defaults.
8490
run_args=(--detach --rm --env PHP_OPCACHE_ENABLE=1)
8591
if [ -n "$http_port" ]; then
8692
# The container runs unprivileged, so the mounted document root must be world readable.

‎src/common/etc/entrypoint.d/0-container-info.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ Brought to you by serversideup.net
6363
if [ "$PHP_OPCACHE_STATUS" = "0" ]; then
6464
echo "👉 [NOTICE]: Improve PHP performance by setting PHP_OPCACHE_ENABLE=1 (recommended for production)."
6565
elif [ "$PHP_OPCACHE_VALIDATE_TIMESTAMPS_STATUS" = "0" ]; then
66-
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"
66+
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"
6767
fi

0 commit comments

Comments
 (0)