PHP 5.3-8.5 built from source on Debian trixie: NTS, php-fpm, ionCube/phpBolt
10K+
https://www.php.net/supported-versions.php https://www.php.net/eol.php https://openssl-library.org/source/ https://openssl-library.org/source/old/ https://pecl.php.net/ https://www.ioncube.com/loaders.php
| PHP | OpenSSL | ICU | curl | libpq | SAPI | Loaders | Comments | Status |
|---|---|---|---|---|---|---|---|---|
| 5.3 | 1.0.2u, End Of Life, 20 Dec 2019 | 67.1 | 8.17.0 | 13 | cli fpm | ionCube | End Of Life, 2014-08-14 | Ready ✅ |
| 5.4 | 1.0.2u, End Of Life, 20 Dec 2019 | 67.1 | 8.17.0 | 13 | cli fpm cgi embed | ionCube | End Of Life, 2015-09-03 | Ready ✅ |
| 5.5 | 1.0.2u, End Of Life, 20 Dec 2019 | 67.1 | 8.17.0 | 13 | cli fpm cgi embed | ionCube | End Of Life, 2016-07-10 | Ready ✅ |
| 5.6 | 1.0.2u, End Of Life, 20 Dec 2019 | 67.1 | 8.17.0 | 13 | cli fpm cgi phpdbg embed | ionCube | End Of Life, 2018-12-31 | Ready ✅ |
| 7.0 | 1.1.1w, End Of Life, 11 Sep 2023 | 67.1 | 8.17.0 | 16 | cli fpm cgi | ionCube Bolt | End Of Life, 2019-01-10 | Ready |
| 7.1 | 1.1.1w, End Of Life, 11 Sep 2023 | 67.1 | 8.17.0 | 16 | cli fpm cgi | ionCube Bolt | End Of Life, 2019-12-01 | Ready |
| 7.2 | 1.1.1w, End Of Life, 11 Sep 2023 | 67.1 | 8.17.0 | 16 | cli fpm cgi | ionCube Bolt | End Of Life, 2020-11-30 | Ready |
| 7.3 | 1.1.1w, End Of Life, 11 Sep 2023 | 67.1 | 8.17.0 | 16 | cli fpm cgi | ionCube Bolt | End Of Life, 2021-12-06 | Ready |
| 7.4 | 1.1.1w, End Of Life, 11 Sep 2023 | 73.2 | 8.17.0 | 16 | cli fpm cgi | ionCube Bolt | End Of Life, 2022-11-28 | Ready |
| 8.0 | 1.1.1w, End Of Life, 11 Sep 2023 | 73.2 | 8.17.0 | 16 | cli fpm cgi | Bolt | End Of Life, 2023-11-26 | Ready |
| 8.1 | 3.5.x, LTS, 08 Apr 2030 | system | 8.21.0 | 16 | cli fpm cgi | ionCube Bolt | End Of Life, 2025-12-31 | Ready |
| 8.2 | 3.5.x, LTS, 08 Apr 2030 | system | 8.21.0 | 16 | cli fpm cgi | ionCube Bolt | security, 2026-12 | Ready |
| 8.3 | 3.5.x, LTS, 08 Apr 2030 | system | 8.21.0 | 16 | cli fpm cgi | ionCube Bolt | security, 2027-12 | Ready |
| 8.4 | 3.5.x, LTS, 08 Apr 2030 | system | 8.21.0 | 16 | cli fpm cgi | ionCube Bolt | bugfix, 2028-12 | Ready |
| 8.5 | 3.5.x, LTS, 08 Apr 2030 | system | 8.21.0 | 16 | cli fpm cgi | ionCube Bolt | bugfix, 2029-12 | Ready |
Only supported versions will be automaticly updated at CI.
1.0.2u. PHP 7.0 builds fine against 1.1.1w in practice, so it uses that.1.1.1w.3.5.x is chosen because it is the LTS branch (supported until 08 Apr 2030).
OpenSSL 3.6 and 4.0 are not LTS and are deliberately not used.no-debug-non-zts-<API> combination.
ZTS would break ionCube and phpBolt, so swoole / parallel are out of scope.mod_php (Apache SAPI) is not built. It was removed from php-src in 8.4,
and Apache/nginx both talk to php-fpm over FastCGI instead.phpBolt covers 7.0–8.5 and is unaffected.1.0.2u are limited to TLS 1.2 — TLS 1.3 does not exist in that
OpenSSL branch.Debian Trixie ships versions that are too new for the older PHP branches, and the
system libcurl / libpq link the system OpenSSL 3 — which would put two
different libssl into one process on the 1.1.1 and 1.0.2 branches. Everything
below is therefore built from source in the develop layer and consumed via
COPY --from=builder.
| ICU | PHP | Why this boundary |
|---|---|---|
| system 76 | 8.1–8.5 | ext/intl compiles as C++17 from 8.1; ICU 74+ headers require C++17 |
73.2 | 7.4, 8.0 | last branch whose headers stay C++11-clean |
67.1 | 5.x, 7.0–7.3 | ICU 68 changed operator== return type UBool → bool |
BreakIterator::operator== to return bool. PHP ≤ 7.3
overrides it with UBool, which C++ rejects as a conflicting return type.ext/intl uses -std=c++11
until PHP 8.1.using namespace icu;. PHP 7.0 relies on it,
hence -DU_USING_ICU_NAMESPACE=1 in CXXFLAGS for that branch only.icu-config, not pkg-config — ICU_CFLAGS /
ICU_LIBS are ignored there. Use --with-icu-dir=${ICU_67_DIR} instead..pc files carry no -L, so on 7.1+ the pkg-config result is
overridden explicitly:
export ICU_CFLAGS="-I${ICU_67_INC_DIR}"
export ICU_LIBS="-L${ICU_67_LIB_DIR} -licui18n -licuuc -licudata -licuio"
/usr/include/unicode) and the unversioned
.so / .a symlinks. Otherwise -licuuc silently resolves to ICU 76 and the
link fails with undefined reference to ..._73.| curl | OpenSSL | PHP |
|---|---|---|
8.21.0 | 3.5 | 8.1–8.5 |
8.17.0 | 1.1.1 | 7.0–8.0 |
8.17.0 | 1.0.2 | 5.x |
8.18.0 removed OpenSSL 1.1.1 support, so 8.17.0 is the last usable
release for those branches. That variant is frozen — no upstream fix will
ever land for it again.ldap, ssh2, rtmp, gssapi, psl: every one of those
links the system OpenSSL and would drag libssl.so.3 back into the process.
Consequence — this curl speaks http/https/ftp/ftps/file/ws/wss
only. No ldap://, scp://, sftp://, rtmp://, no Kerberos auth.--with-ca-bundle=/etc/ssl/certs/ca-certificates.crt is mandatory, otherwise
curl bakes in a build-time path and never sees the EpicMorg CA certs.--enable-option-checking=fatal is not curl's default — without it an
unknown or renamed flag is silently ignored and you end up with ldap inside
while believing you disabled it.| libpq | OpenSSL | PHP |
|---|---|---|
16 | 3.5 / 1.1.1 | 7.0–8.5 |
13 | 1.0.2 | 5.x |
src/interfaces/libpq + src/include + src/bin/pg_config are built —
a full PostgreSQL build takes twenty minutes and pulls in dependencies we do
not need.pg_config is required: PHP's --with-pgsql=DIR reads the include and lib
paths from it.gssencmode
and LDAP lookups are unavailable.| Library | Version | OpenSSL | PHP | Prefix |
|---|---|---|---|---|
| libzip | 1.11.4 | 1.1.1 | 7.0–8.0 | ${EMG_LOCAL_BASE_DIR}/libzip/1.11.4/openssl111 |
| libmemcached | 1.1.4 | none | 7.0–8.0 | ${EMG_LOCAL_BASE_DIR}/libmemcached/1.1.4 |
Debian's libzip5 (zip encryption) and libmemcached (libhashkit,
libsasl2) link the system libcrypto.so.3. On the 1.1.1 branches that put
libcrypto.so.3 into php / php-fpm (via ext/zip) and into memcached.so,
next to our libcrypto.so.1.1 — a library duality. Both are therefore built in
the builder stage of 7.0–8.0:
ZipArchive
encryption keeps working. libzip-dev is not installed.libsasl2
links libcrypto.so.3 as well) and with its bundled AES instead of OpenSSL,
so it links no libcrypto at all. Consequence: memcached.use_sasl /
Memcached::setSaslAuthData() are unavailable on 7.0–8.0.
libmemcached-dev is not installed.8.1+ keep the Debian libraries: the system libcrypto.so.3 has the same SONAME
as our 3.5, and the RPATH of php / memcached.so makes it resolve to ours.
5.x ships no memcached (see the Dockerfile) and uses PHP's bundled libzip.
PHP is built with the system compiler (Debian cc, gcc 14), not with
epicmorg/gcc:N. PHP's autoconf prefers cc; Python's configure looks for
gcc first and picks ours. Both are intentional. Do not force CC=gccNN: with
an older image compiler every configure test of the old branches fails.
PHP publishes no tarball for an EOL branch, but for some advisories it pushes the fix to a branch of
php-src. Those official patches are applied on top of the last release tarball (<ver>/tmp/patches/,
applied after extraction, any reject is fatal). The tags stay the same (7.4, 7.4.33, ...): the
version number is unchanged, the image just carries the fix. The image shows what was applied in
PHP_PATCHES (env) and the org.epicmorg.php.patches label.
| PHP | Release | Patches | php-src source |
|---|---|---|---|
| 7.4 | 7.4.33 | CVE-2026-109618, CVE-2026-109619 (advisory 2026-10-21) | PHP-7.4-CVE-2026-109618-109619, commit 712e60f9edfd |
| 8.0 | 8.0.30 | CVE-2026-109618, CVE-2026-109619 | PHP-8.0-CVE-2026-109618-109619, commit 82f8f31b16a0 |
| 8.1 | 8.1.34 | CVE-2026-109618, CVE-2026-109619 | PHP-8.1-CVE-2026-109618-109619, commit 1d7dec178a09 |
Only the fix commit is taken ("Avoid state corruption via bailout"), not the branches' version bumps or CI changes. Nothing was published for 5.x and 7.0-7.3.
pecl.php.net serves part of its REST metadata with Transfer-Encoding: chunked and no Content-Length. PEAR
before 1.10.17 cannot decode that (does not have REST xml available, No releases available), and
pear upgrade would go through the same broken client. So the PEAR toolchain is replaced from local files before the
first pecl call:
| PHP | Bundled PEAR | What the image does |
|---|---|---|
| 5.3, 5.4 | 1.9.x | pecl is not used for the build: every extension tarball is fetched from pecl.php.net/get/<pkg>-<ver>.tgz and built with phpize + explicit ./configure (PEAR 1.10 needs PHP 5.4+, and the old PEAR cannot unpack the current archives anyway) |
| 5.5 | 1.10.1 | PEAR 1.10.18 + Archive_Tar 1.4.14 (1.6 needs PHP 5.6), Console_Getopt, Structures_Graph, XML_Util - fetched with aria2c, sha256-checked, pear upgrade --force --nodeps <files> |
| 5.6 - 8.1 | 1.10.7 - 1.10.16 | the same with Archive_Tar 1.6.0 |
| 8.2 - 8.5 | 1.10.18 | as shipped |
The image's pear / pecl therefore work against pecl.php.net for later pecl install too.
Applied inside the builder stage. Each one is a symptom of Trixie being far newer than the PHP branch being built.
| Workaround | Branches | Reason |
|---|---|---|
ac_cv_func_readdir_r=no | ≤ 7.3 | Modern glibc declares readdir_r via __REDIRECT; PHP's autoconf test misdetects it and emits a two-argument call |
freetype-config shim | ≤ 7.3 | Debian dropped freetype-config; PHP's gd detection still calls it |
sed patch on main/php_config.h | 7.0, 7.1 | zend_sprintf is declared outside extern "C"; ext/intl includes the header both inside and outside it, giving conflicting linkage |
-Wno-error=incompatible-pointer-types | ≤ 7.2 | fopencookie seeker signature mismatch; gcc 14 turned this into an error |
-fpermissive in CXXFLAGS | ≤ 7.1 | C++ strictness in ext/intl |
-DU_USING_ICU_NAMESPACE=1 | 7.0 | ICU 65 disabled the implicit using namespace icu; |
--with-icu-dir= | 7.0 | ext/intl uses icu-config, not pkg-config |
The php_config.h patch runs after ./configure because that header is
generated. A future make distclean would regenerate it unpatched.
Each PHP branch has its own ZEND_MODULE_API_NO, which defines the extension
directory name and the loader blob to use. It is written to
${PHP_DIR}/.module-api at build time — never hardcode it, read it:
api="$(cat ${PHP_DIR}/.module-api)"
cp some_loader.so /usr/lib/php/${api}/
| PHP | API |
|---|---|
| 5.5 | 20121212 |
| 5.6 | 20131226 |
| 7.0 | 20151012 |
| 7.1 | 20160303 |
| 7.2 | 20170718 |
| 7.3 | 20180731 |
| 7.4 | 20190902 |
| 8.0 | 20200930 |
| 8.1 | 20210902 |
| 8.2 | 20220829 |
| 8.3 | 20230831 |
| 8.4 | 20240924 |
| 8.5 | 20250925 |
Compiled into PHP at ./configure time. Availability varies between branches —
where an extension does not exist for a given PHP version, it is simply absent.
bcmath bz2 calendar ctype curl date dba dom
exif ffi fileinfo filter ftp gd gettext gmp
hash iconv intl json libxml mbstring mysqli mysqlnd
opcache openssl pcntl pcre pdo pdo_mysql pdo_pgsql pdo_sqlite
pgsql phar posix random readline reflection session shmop
simplexml soap sockets sodium spl sqlite3 standard sysvmsg
sysvsem sysvshm tidy tokenizer xml xmlreader xmlwriter xsl
zip zlib
PHP 8.5 additionally ships lexbor (HTML5 parser) and uri.
Anything not in this list can be added on top — see Helper scripts below.
Versions are pinned in the Dockerfile. pecl install <ext> without a version
resolves to "latest at build time" and makes builds non-reproducible.
Newer releases keep dropping old PHP branches, so the pinned set differs per
branch. When pecl refuses to install, check the extension's page for the last
release that still lists that PHP version.
| Extension | 8.5 | 8.1–8.4 | 7.2–8.0 | 7.0–7.1 | Priority |
|---|---|---|---|---|---|
igbinary | 3.2.17RC1 | 3.2.16 | 3.2.16 | 3.2.16 | 15 |
msgpack | 3.0.1 | 3.0.1 | 2.2.0 | 2.2.0 | 15 |
apcu | 5.1.28 | 5.1.28 | 5.1.24 | 5.1.24 | 20 |
redis | 6.3.0 | 6.3.0 | 6.0.2 | 5.3.7 | 20 |
memcached | 3.4.0 | 3.4.0 | 3.2.0 | 3.2.0 | 20 |
imagick | 3.8.1 | 3.8.1 | 3.7.0 | 3.7.0 | 20 |
timezonedb | 2026.3 | 2026.3 | 2026.3 | 2026.3 | 30 |
igbinary 3.2.17RC1 on 8.5 is temporary. 3.2.16 does not build against
PHP 8.5 (php_smart_string.h moved). Move back to a stable release once
3.2.17 final ships.igbinary and msgpack must be installed before
redis and memcached, otherwise those are compiled without serializer
support and silently fall back to the native PHP serializer — which breaks
reading existing cache data written with igbinary.redis and memcached are built via pecl download + explicit ./configure
rather than pecl install, because pecl's interactive prompts change order
between releases and silently produce wrong flags
(observed: --with-libmemcached-dir=no, --with-system-fastlz).pie is installed on 8.1+ for forward
compatibility.Added so images built on Debian's or sury's php*-* packages can move to these. Built from the pecl.php.net tarball
with phpize (no pecl prompts, no PEAR client) or, for core extensions, from this PHP's own source tree
(php-ext-install); none of their libraries links OpenSSL. On by default are only the light ones; the others are
shipped as .so with a mods-available/<ext>.ini but off - turn them on in a downstream layer or on the
container: RUN php-enmod pspell, or a mounted conf.d/*.ini.
| Extension | Default | 8.5 | 8.1–8.4 | 8.0 | 7.3–7.4 | 7.1–7.2 | 7.0 | 5.3–5.6 |
|---|---|---|---|---|---|---|---|---|
yaml | on | 2.3.0 | 2.3.0 | 2.3.0 | 2.3.0 | 2.3.0 | 2.0.4 | 1.3.2 |
mailparse | on | 3.2.0 | 3.2.0 | 3.2.0 | 3.2.0 | 3.1.3 | 3.1.3 | 2.1.6 |
memcache | on | git ac8e8c5* | 8.2 | 8.2 | 4.0.5.2 | 4.0.5.2 | 4.0.5.2 | 2.2.7 |
gnupg | on | 1.5.4 | 1.5.4 | 1.5.4 | 1.5.4 | 1.5.4 | 1.5.4 | 1.5.4 |
gearman | on | 2.2.1 | 2.2.1 | 2.2.1 | 2.2.1 | 2.1.4 / 2.2.1 | 2.1.3 | 1.1.2 |
opentelemetry | off | 1.4.2 | 1.4.2 | 1.1.3 | - | - | - | - |
xmlrpc | off | PECL 1.0.0RC3 | PECL 1.0.0RC3 | PECL 1.0.0RC3 | core | core | core | core |
pspell | off | PECL 1.0.1 | core (8.4: PECL 1.0.1) | core | core | core | core | core |
enchant | off | core | core | core | - | - | - | - |
ldap (+SASL) | off | core | core | core | core | core | core | core |
ssh2 | off | 1.5.0 | 1.5.0 | 1.5.0 | 1.4.1 | 1.4.1 | 1.4.1 | 0.13 |
* no PECL release of memcache builds on 8.5 yet (php_smart_string.h moved); the upstream commit with the fix is
pinned by sha256 until one ships.
ldap and ssh2 link this PHP's own OpenSSL, not Debian's (whose libldap / libsasl2 / libssh2 pull the system
libssl3): Cyrus SASL 2.1.28, the OpenLDAP client libraries (2.6.15; 2.4.59 on the 1.0.2 branches) and libssh2
1.11.1 are built per OpenSSL branch into cyrus-sasl/2.1.28/openssl<branch>, openldap/<ver>/openssl<branch>,
libssh2/1.11.1/openssl<branch> under /usr/local/share/epicmorg (RPATH, nothing in ld.so.conf).
Not provided: enchant before 8.0 (needs enchant 1, gone from trixie). imap is postponed (UW-IMAP c-client,
PECL-only and deprecated from 8.4).
Vendor binaries, no source available.
| Loader | Load type | Priority | PHP range | Source |
|---|---|---|---|---|
ionCube | zend_extension | 05 | 4.1 – 8.5, no 8.0 | downloaded from ioncube.com at build |
phpBolt | extension | 06 | 7.0 – 8.5 | shipped in-repo under usr/local/lib/php/ |
perforce | extension | 20 | 7.0 – 8.5 | downloaded from ftp.perforce.com at build |
ionCube is a Zend extension, phpBolt is a regular one — despite both
being bytecode loaders. Enabling phpBolt as zend_extension loads nothing._ts blob. _ts is the ZTS variant and will not load.ionCube must be loaded before OPcache, hence priority 05.phpBolt archive filenames are inconsistent upstream
(linux 64 - php7.3, linux 64-php8.2, linux-php8.5), so the blob name is an
explicit ARG per image rather than derived from PHP_VERSION.phpBolt registers itself as Bolt on 7.x and bolt on 8.x — match
case-insensitively.The p4php blob lives in a different Perforce release per PHP branch, and the OpenSSL variant must match the branch:
| PHP | Release | Blob |
|---|---|---|
| 7.0–7.1 | r20.1 | perforce_php7N-ssl1.1.1.so |
| 7.2–8.0 | r24.1 | perforce_php7N-ssl1.1.1.so |
| 8.1 | r25.2 | perforce_php81-ssl3.so |
| 8.2–8.5 | r26.1 | perforce_php8N-ssl3.so |
OpenSSL is statically linked inside the blob, so it does not add a second
libssl to the process. The r24.1+ blobs are ~77 MB for that reason — do not
strip them.
Vanilla PHP has a single ini scan dir. Debian has per-SAPI conf.d plus
mods-available. We keep one real conf.d and point every Debian path at it,
so existing configs and scripts keep working.
${EMG_LOCAL_BASE_DIR}/php/<ver>/
├── bin/ php, php-cgi, php-config, phpize, pear, pecl, phpdbg
├── sbin/ php-fpm
├── etc/
│ ├── php.ini
│ ├── php-fpm.conf, php-fpm.d/
│ ├── mods-available/ real .ini files
│ ├── conf.d/ symlinks: NN-<mod>.ini -> ../mods-available/<mod>.ini
│ ├── cli/ conf.d -> ../conf.d, php.ini -> ../php.ini
│ ├── fpm/ conf.d -> ../conf.d, php.ini -> ../php.ini
│ └── cgi/ conf.d -> ../conf.d, php.ini -> ../php.ini
├── lib/php/extensions/no-debug-non-zts-<API>/
├── include/php/
├── src/ -> /usr/local/src/php/<version>
└── .module-api <API>
Compatibility symlinks:
/etc/php/<ver> -> ${EMG_LOCAL_BASE_DIR}/php/<ver>/etc
/usr/lib/php/<API> -> ${EMG_LOCAL_BASE_DIR}/php/<ver>/lib/php/extensions/no-debug-non-zts-<API>
Deliberate difference from Debian: extensions are enabled for all SAPIs at once. Per-SAPI extension sets (e.g. xdebug only for cli) are not supported — use a separate container for that.
05 ionCube must precede OPcache
06 phpBolt
10 OPcache and other zend_extensions
15 serializers igbinary, msgpack
20 regular extensions (default)
30 timezonedb overrides the built-in tz database, load last
Derived from docker-library/php (MIT)
and Debian php-common conventions.
| Script | Purpose |
|---|---|
php-ext-install | build + install + enable a bundled extension from source |
php-ext-configure | phpize + ./configure for a bundled extension |
php-ext-enable | write mods-available/<mod>.ini for a built .so and enable it |
php-enmod | link mods-available/<mod>.ini into conf.d/NN-<mod>.ini |
php-dismod | remove the conf.d symlink (ini stays in mods-available) |
phpenmod / phpdismod exist as aliases for drop-in compatibility with
Debian-era Dockerfiles.
# bundled extension, from the source tree shipped in the image
php-ext-install ldap snmp
# PECL
pecl install redis-6.3.0 && php-ext-enable redis
# vendor blob
api="$(cat ${PHP_DIR}/.module-api)"
cp loader.so /usr/lib/php/${api}/
php-ext-enable --zend --priority 05 loader
# turn something off
php-dismod xdebug
php-ext-enable decides extension= vs zend_extension= from an explicit list
inside the script — autodetection would silently produce a module that loads but
misbehaves, which is worse than a hard error. Use --zend to override.
Two upstream defaults are dangerous in a container and are overridden:
pm.max_requests = 500 — upstream default is 0, meaning a worker lives
forever. A slow leak then turns into an OOM after a few days of uptime.request_terminate_timeout = 300s — upstream default is 0. One
unresponsive backend blocks every worker, max_children is exhausted and the
site is down while nothing has technically crashed. This also covers time spent
in blocking I/O, which max_execution_time does not.clear_env = no — otherwise container environment variables never reachTruncated - full README: https://github.com/EpicMorg/docker/blob/master/linux/ecosystem/apps/php/README.md
Content type
Image
Digest
sha256:67d2481dc…
Size
1 GB
Last updated
4 days ago
docker pull epicmorg/php:8.0