EOL PHP 5.6-8.1 patched against php2shell: Composer, WP-CLI, msmtp, Supercronic, env extensions
9.5K
PHP 5.6 and 7.1 to 8.1, branches PHP no longer supports, patched against
php2shell (CVE-2026-109618,
CVE-2026-109619),
with the same runtime as frugan/php:
Composer, WP-CLI, msmtp for mail(), Supercronic, extra locales, and a set of
extensions you switch on with an environment variable.
Warning
These images are a way to keep legacy sites running while they are moved to a supported PHP, not a place to stay. Nobody audits these branches any more: the fixes below are the ones someone backported, and what nobody backported stays open. Anything that can run on PHP 8.2 or later belongs on [frugan/php](https://github.com/xaas-hub/docker-php).
There are no official images for these branches, so PHP is compiled here, the
way docker-library/php compiles it,
on Debian bookworm. The source of each branch is pinned by commit in
versions.json:
| PHP | Source | php2shell fix | Upload progress | OpenSSL |
|---|---|---|---|---|
| 8.1 | Remi Collet's security tree | official, in the tree | disabled | 3 |
| 8.0 | Remi Collet's security tree | official, in the tree | disabled | 1.1.1w |
| 7.4 | Remi Collet's security tree | official, in the tree | disabled | 1.1.1w |
| 7.3 | Remi Collet's security tree | community patch (enhanced-remicollet) | works | 1.1.1w |
| 7.2 | Remi Collet's security tree | community patch (enhanced-remicollet) | works | 1.1.1w |
| 7.1 | Remi Collet's security tree | community patch (enhanced-remicollet) | works | 1.1.1w |
| 5.6 | Remi Collet's security tree (OpenSSL 1.1) | community patch (enhanced-remicollet) | works | 1.1.1w |
PHP-*-CVE-2026-109618-109619 branches, adapted to the
tree: those branches lack every other fix since the branch went end of
life.patches/ holds verbatim copies, as of commit 41fa62f. They are
not reviewed by the PHP Group. Its patches for Remi's 7.4, 8.0 and 8.1 trees
are not used: they replace the official fix to bring the session upload
progress back, which CVE-2026-115560 wants off anyway.patches/compat/, only where today's compilers need them:
5.6 gets the arm64 fix of ZEND_SIGNED_MULTIPLY_LONG that 7.0 got in 2016.41fa62f, does not
build there: it removes the max_multipart_body_parts entry from
main/main.c and adds to main/rfc1867.c a second copy of the code that
reads it, both already in the tree. 7.0 comes back once that patch builds.All variants of the fix make PHP stop the process when it runs out of memory, instead of unwinding the request:
memory_limit ends with the fatal error on stderr and the
FPM worker exits: no shutdown functions, no destructors, no error page from
the application. The web server answers 502, and FPM starts a new worker.session.upload_progress.enabled has no effect); on the other branches the
community patch fixes it and keeps it working, but the image turns it off by
default against CVE-2026-115560, which no patch fixes yet.Raise memory_limit where a site used to hit it.
| Composer | composer: 2.2 LTS on PHP < 7.2.5, the latest 2.x from there |
| WP-CLI | wp, with wp profile preinstalled |
| msmtp | sendmail_path, configured from MSMTP_* |
| Supercronic | cron for containers |
| install-php-extensions | for images built FROM this one |
| git, unzip, patch, less, curl, tzdata, locales | what Composer and WP-CLI expect to find |
Extensions always enabled: bcmath, exif, gd, mysqli, opcache, pdo_mysql, zip, plus sodium from 7.2 on, intl from 7.4 on, and those every build of PHP has. intl is missing before 7.4 because its sources do not build against the ICU of bookworm.
Optional, enabled with PHP_EXTENSIONS: apcu, bz2, calendar, gettext, gmp,
igbinary, imagick, ldap, mcrypt, memcached, msgpack, pcntl, pdo_pgsql, pgsql,
redis, soap, sockets, tidy, xsl, each in the last release that supports the
branch. The -dev images add Xdebug: 3.x, loaded with XDEBUG_MODE=off,
from 7.2 on; 2.x before, where XDEBUG_MODE has no effect.
php.ini is php.ini-production, with expose_php = Off and
session.upload_progress.enabled = Off (a mitigation PHP recommends for
CVE-2026-115560 and CVE-2026-109619; PHP_INI_SESSION__UPLOAD_PROGRESS__ENABLED=1
turns it back on before 7.4).
The same images, with the same tags, are published to two registries: use either.
| Registry | Image |
|---|---|
| Docker Hub | frugan/php-legacy |
| GitHub Packages | ghcr.io/xaas-hub/php-legacy |
frugan/php-legacy:<php>-<variant>[-dev][-v<release>]
ghcr.io/xaas-hub/php-legacy:<same>
| Tag | Content |
|---|---|
7.4-fpm | PHP 7.4, latest release of this image: updated daily |
7.4.33-fpm | the same image: these branches get no new PHP releases |
7.4-fpm-v1.2.3 | the image as of release 1.2.3, never rebuilt |
7.4-fpm-dev | the same three forms, with Xdebug |
PHP: 5.6, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1. Variants: fpm,
cli. Platforms: linux/amd64, linux/arm64. There is no latest.
The rolling tags are the ones to run: a Debian security update reaches them within a day, and a new backport as soon as a release of this repository picks it up.
services:
php:
image: frugan/php-legacy:7.4-fpm
init: true
environment:
APP_ENV: production
USER_ID: 1000 # your uid on the host: files written by PHP are yours
GROUP_ID: 1000
PHP_EXTENSIONS: apcu,imagick,redis
PHP_INI_MEMORY_LIMIT: 256M
PHP_INI_UPLOAD_MAX_FILESIZE: 64M
PHP_INI_POST_MAX_SIZE: 64M
FPM_POOL_PM__MAX_CHILDREN: 20
MSMTP_HOST: smtp.example.com
MSMTP_USER: [email protected]
MSMTP_PASSWORD_FILE: /run/secrets/smtp_password
secrets:
- smtp_password
volumes:
- ./html:/var/www/html
secrets:
smtp_password:
file: ./secrets/smtp_password
FPM listens on port 9000, like the official images: point nginx, Caddy or Traefik there.
Commands inside a running container:
docker compose exec -u www-data php wp plugin list
docker compose exec -u www-data php composer install
-u www-data matters: docker exec does not go through the entry point and
runs as root otherwise.
Everything is read at container start: change the environment and recreate the container. The variables are those of frugan/php, with the differences noted below.
| Variable | Default | |
|---|---|---|
PHP_EXTENSIONS | optional extensions, separated by commas or spaces |
redis and memcached bring igbinary and msgpack with them. An extension
that is not in the image stops the container with the list of the available
ones. For anything else, build on top of this image:
FROM frugan/php-legacy:7.4-fpm
RUN install-php-extensions mongodb
| Variable | |
|---|---|
PHP_INI_<NAME> | any php.ini directive |
FPM_POOL_<NAME> | any directive of the www pool (fpm images) |
<NAME> is the directive in upper case, with __ for the dot:
PHP_INI_MEMORY_LIMIT=256M memory_limit = "256M"
PHP_INI_DATE__TIMEZONE=Europe/Rome date.timezone = "Europe/Rome"
FPM_POOL_PM=static pm = "static"
FPM_POOL_PM__MAX_CHILDREN=20 pm.max_children = "20"
Values may not contain double quotes or newlines. PHP_INI_DIR and
PHP_INI_SCAN_DIR are not directives. Array directives such as
php_admin_value[...] have no variable form: mount a file in
/usr/local/etc/php-fpm.d/ or /usr/local/etc/php/conf.d/. A directive the
branch does not know is an error for FPM and is ignored by PHP.
A PHP_INI_* or FPM_POOL_* variable set to an empty value is written too,
as directive = "": that is how a directive is cleared
(PHP_INI_DISABLE_FUNCTIONS=). In a compose file, ${MEMORY_LIMIT} left
unset therefore clears memory_limit instead of keeping the default: write
${MEMORY_LIMIT:-128M}.
| Variable | Default | |
|---|---|---|
APP_ENV | production | production, staging, develop, anything else |
In production PHP reads the standard .user.ini (cached for 300 seconds). In
any other environment it reads .user-<APP_ENV>.ini instead, with
user_ini.cache_ttl = 0 so that edits apply on the next request; set
PHP_INI_USER_INI__CACHE_TTL for another value.
The image never sets APP_ENV in the environment itself: Laravel's .env
keeps working when the variable is not passed. When it is, FPM hands it to the
workers like every other variable (clear_env = no, as in the official
images).
| Variable | Default | |
|---|---|---|
USER_ID, GROUP_ID | ids www-data takes at start | |
EXEC_USER | www-data (cli), empty (fpm) | user the command runs as |
Default: remapping. The container starts as root, gives www-data the
ids in USER_ID and GROUP_ID, and FPM runs its workers as www-data: what
PHP writes on a bind mount belongs to your user on the host. The cli images
also run their command as www-data; EXEC_USER= runs it as root.
Alternative: rootless. Start the container with user: "1000:1000" (or
--user): nothing runs as root, the ids are the container's own, and
USER_ID, GROUP_ID, EXEC_USER and LOCALES are ignored. With
read_only: true, give it a writable /run/docker-php:
user: "1000:1000"
read_only: true
tmpfs:
- /run/docker-php
- /tmp
| Variable | Default | |
|---|---|---|
MSMTP_HOST | SMTP server; nothing is generated without it | |
MSMTP_PORT | 587 | |
MSMTP_TLS | starttls | starttls, on (implicit, default on port 465), off |
MSMTP_TLS_CERTCHECK | on | |
MSMTP_USER | enables authentication | |
MSMTP_PASSWORD_FILE | file holding the password, e.g. a Docker secret | |
MSMTP_PASSWORD | the password itself, when a file is not an option | |
MSMTP_FROM | envelope sender, when PHP does not pass one | |
MSMTP_LOGFILE | - logs every message to the container output |
sendmail_path is /usr/bin/msmtp -t, which reads /etc/msmtprc. The
generated file is readable by root and www-data only, and MSMTP_PASSWORD
is removed from the environment before PHP starts.
| Variable | Default | |
|---|---|---|
SUPERCRONIC_ENABLED | start Supercronic next to the command | |
SUPERCRONIC_CRONTAB | /etc/crontab | |
SUPERCRONIC_FLAGS | e.g. -json, -split-logs, -overlapping |
Jobs run as www-data. As in frugan/php, a separate container from the cli
image is the cleaner setup:
cron:
image: frugan/php-legacy:7.4-cli
init: true
restart: unless-stopped
command: ["supercronic", "/etc/crontab"]
environment:
USER_ID: 1000
GROUP_ID: 1000
volumes:
- ./html:/var/www/html
- ./crontab:/etc/crontab:ro
| Variable | |
|---|---|
LOCALES | it_IT.UTF-8, de_DE.UTF-8, full lines such as en_US ISO-8859-1, or all |
Generated at start, only when the list changes. Rootless containers cannot
generate them: build them in with --build-arg LOCALES="it_IT.UTF-8".
Files /docker-entrypoint.d/*.sh are sourced in order after the steps above
and before the command, as root unless the container is rootless.
The fpm images ship a HEALTHCHECK that asks the pool for /ping over
FastCGI, at the address in FPM_POOL_LISTEN (9000 by default). Unlike
frugan/php, the ping requests show up in the access log: FPM has no setting to
leave them out before 8.2.
docker build --build-arg PHP_VERSION=5.6 --build-arg PHP_VARIANT=cli \
--target cli-dev -t php-legacy:5.6-cli-dev .
| Argument | Default |
|---|---|
PHP_VERSION | 8.1; a key of versions.json |
PHP_VARIANT | fpm; must match the target |
BASE_IMAGE | debian:bookworm-slim |
PHP_EXTENSIONS_CORE | the always enabled extensions |
PHP_EXTENSIONS_OPTIONAL | the ones PHP_EXTENSIONS enables |
LOCALES | locales generated at build time |
Targets: fpm (default), fpm-dev, cli, cli-dev. Compiling PHP takes
several minutes per branch and architecture. The end-to-end test runs against
any of them, and checks that the php2shell fix is in:
tests/smoke.sh php-legacy:5.6-cli-dev cli-dev
Every merge to main goes through semantic-release: the commit types decide
the version (see CONTRIBUTING), and the release
publishes all images with their -v tags and refreshes the rolling ones.
A fix to PHP reaches the images through a commit that moves a pin in
versions.json or updates a file in patches/, and the release that
follows. Every day the Debian base image is checked, and when it moved all
images are rebuilt from the latest release, tested and republished. Once a
week everything is rebuilt anyway, for Composer, install-php-extensions and
the PECL extensions.
For your contributions please use:
See CONTRIBUTING for detailed guidelines.
Copyright (c) 2026 Frugan. MIT, see the LICENSE file.
The files in patches/ derive from php-src and are under the
PHP License 3.01; those in
build/docker-library/ come from docker-library/php, under its
MIT license. PHP, OpenSSL, Composer, WP-CLI,
msmtp, Supercronic and the bundled extensions keep their own licenses.
Content type
Image
Digest
sha256:3569e231b…
Size
240.6 MB
Last updated
about 19 hours ago
docker pull frugan/php-legacy:7.4.33-cli-dev