In short: To run PHP in containers, choose the suitable variant of the official image (fpm, apache or cli), activate the production php.ini, install only the extensions you need and set memory and process limits that fit together. Containers are immutable: a PHP security update such as the one for PHP2Shell takes effect only when the image has been rebuilt and every container restarted. This rebuild therefore belongs in every container operation as a fixed process.
Why PHP in containers?
Containers package PHP, extensions and configuration into an image that runs the same on every server. For companies this means: fewer surprises between test and production, faster recovery and clear version states. For security, however, it also means a new duty: nobody applies updates automatically inside a container.
This page covers day-to-day operation. How to patch existing containers against PHP2Shell concretely is described under Patching PHP in Docker containers.
Choosing the right image variant
| Variant | Contents | Typical use |
|---|---|---|
php:<version>-fpm |
PHP-FPM, needs an upstream web server | production with Nginx, Caddy or an ingress |
php:<version>-apache |
Apache with mod_php, mpm_prefork by default | simple setups, older applications |
php:<version>-cli |
command line, the only variant that also contains php-cgi | cron jobs, queue workers, build steps |
…-alpine |
based on Alpine Linux with musl | small images, adjustments needed for some extensions |
…-bookworm, …-trixie |
based on the named Debian version | fixed base for reproducible builds |
For new projects, the fpm variant with a separate web server container is generally recommended. With mod_php, the PHP team sees the highest risk in connection with PHP2Shell; anyone using the apache image operates exactly this mode.
Building a production image
A lean example for an application with MySQL access:
FROM php:8.4-fpm-alpine
# Activate the production configuration (strongly recommended by the image docs)
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# Only the extensions that are needed
RUN docker-php-ext-install -j"$(nproc)" pdo_mysql
# Own settings
COPY zz-sicherheit.ini "$PHP_INI_DIR/conf.d/"
# Code belongs to root; the FPM workers run as www-data and can only read it
COPY . /var/www/html
The image's helper scripts take care of building extensions: docker-php-ext-configure for options, docker-php-ext-install for building, docker-php-ext-enable for activating, for example after pecl install. If the source code is unpacked with docker-php-source, according to the docs it should be deleted again in the same layer. The file zz-sicherheit.ini could look like this:
expose_php = Off
display_errors = Off
log_errors = On
session.use_strict_mode = 1
session.cookie_httponly = 1
Two further principles make images more robust. First, the delivered image contains only what is needed at runtime; compilers, package caches and test files belong in a separate build stage. Second, the user under which PHP runs may write only where necessary, for example in upload or cache directories on mounted volumes. Many teams additionally run the container with a read-only file system. A PHP file injected into it then has a much harder time staying, and every restart restores the clean state of the image. This does not replace an investigation after an incident, but it noticeably reduces the attack surface.
Processes, memory and logs in the container
Three levers determine whether a PHP container runs stably.
- Number of workers:
pm.max_childrenin the FPM pool determines how many requests are handled at the same time. Each worker can occupy up tomemory_limit. - Memory limit of the container: It limits all processes together. As a rough rule, the number of workers times memory per worker plus a reserve must fit under the limit, otherwise the system terminates processes.
- Logs: Containers write logs to standard output. In PHP-FPM,
catch_workers_output = yesroutes the workers' output into the main log; without the setting it ends up nowhere, according to the manual.
Also helpful is request_terminate_timeout, which ends individual requests after a period of time when max_execution_time does not take effect. How to run containers in a cluster is described in Patching PHP in Kubernetes.
Containers and security: what PHP2Shell has shown
With PHP2Shell, containers play a dual role. On the one hand they are a help: as an interim measure for CVE-2026-109618, php.net recommends combining memory_limit=-1 with a memory limit via container or cgroup, ideally in such a way that only the affected worker is terminated. According to php.net, RLIMIT_AS and RLIMIT_DATA are unsuitable for this. This measure is not complete protection and does not replace the update.
On the other hand, containers are a trap if images are not rebuilt. An image with PHP 8.4.26 remains vulnerable no matter how often the container restarts. Only a rebuild to 8.4.27 or newer and the replacement of all running containers close the gap. The state can be checked directly in the running container:
docker exec <container> php -v
docker exec <container> php-fpm -v
docker exec <container> php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
If the last command returns bool(false), PHP does not know the directive introduced with the fixed versions; according to the manual, that version is then affected. In the long term only an automatic rebuild helps, as described in CI/CD: rebuilding PHP images automatically.
Define the exact PHP version in the tag in the Dockerfile and raise it deliberately. That way it is traceable in the code when which version was rolled out.
How does PHP SOS help?
As part of server hardening for PHP web servers, we also look after containers:
- Check images: Base image, PHP version, extensions and configuration of every image.
- Harden the configuration: Production ini, lean extension list, suitable worker and memory limits.
- Set up rebuilds: A process that rebuilds and rolls out images on new PHP releases.
- Verify the runtime: We demonstrate that the corrected version is running in every running container.
Frequently asked questions
Which variant of the PHP image is the right one?
For Nginx or another proxy, the fpm variant; for a simple setup with Apache, the apache variant; for scripts and cron jobs, the cli variant. Alpine variants are smaller but based on musl instead of glibc, which shows up with some extensions.
Is PHP in a container updated automatically?
No. A running container does not change. You must rebuild the image with the new PHP version and restart the containers. Only then does the corrected version run.
How do memory_limit and the container limit relate?
memory_limit limits each individual PHP request, the container limit the whole container. Both must fit together: the number of workers times memory per worker should be below the container limit.
Do I need a php.ini in containers?
Yes. The official images ship php.ini-development and php.ini-production as templates. For production, the image documentation expressly recommends the production template; your own settings go into conf.d.