Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br /><br />⚠️ 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.<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).|
| [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. |
Expand Down
4 changes: 2 additions & 2 deletions docs/content/docs/2.image-variations/frankenphp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 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?

- **Volume-based**: Best for sites heavily dependent on third-party plugins or when you need maximum WordPress ecosystem compatibility
Expand Down
63 changes: 63 additions & 0 deletions docs/content/docs/5.guide/5.major-version-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,69 @@ 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 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).

```yml [compose.yml] {3}
services:
php:
image: serversideup/php:8.5-fpm-nginx-v4.5.1
```

### 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 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
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.

### 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
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` | `32` |
| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `10000` | `32531` |
| `PHP_REALPATH_CACHE_TTL` | `120` | `600` |

### New variables
- `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)

### 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.

Expand Down
Loading
Loading