Sign inSign up

epicmorg/php

By epicmorg

•Updated 4 days ago

PHP 5.3-8.5 built from source on Debian trixie: NTS, php-fpm, ionCube/phpBolt

Image
0

10K+

epicmorg/php repository overview

⁠Version Compability

⁠Status of PHP versions

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⁠

PHPOpenSSLICUcurllibpqSAPILoadersCommentsStatus
5.31.0.2u, End Of Life, 20 Dec 201967.18.17.013cli fpmionCubeEnd Of Life, 2014-08-14Ready ✅
5.41.0.2u, End Of Life, 20 Dec 201967.18.17.013cli fpm cgi embedionCubeEnd Of Life, 2015-09-03Ready ✅
5.51.0.2u, End Of Life, 20 Dec 201967.18.17.013cli fpm cgi embedionCubeEnd Of Life, 2016-07-10Ready ✅
5.61.0.2u, End Of Life, 20 Dec 201967.18.17.013cli fpm cgi phpdbg embedionCubeEnd Of Life, 2018-12-31Ready ✅
7.01.1.1w, End Of Life, 11 Sep 202367.18.17.016cli fpm cgiionCube BoltEnd Of Life, 2019-01-10Ready
7.11.1.1w, End Of Life, 11 Sep 202367.18.17.016cli fpm cgiionCube BoltEnd Of Life, 2019-12-01Ready
7.21.1.1w, End Of Life, 11 Sep 202367.18.17.016cli fpm cgiionCube BoltEnd Of Life, 2020-11-30Ready
7.31.1.1w, End Of Life, 11 Sep 202367.18.17.016cli fpm cgiionCube BoltEnd Of Life, 2021-12-06Ready
7.41.1.1w, End Of Life, 11 Sep 202373.28.17.016cli fpm cgiionCube BoltEnd Of Life, 2022-11-28Ready
8.01.1.1w, End Of Life, 11 Sep 202373.28.17.016cli fpm cgiBoltEnd Of Life, 2023-11-26Ready
8.13.5.x, LTS, 08 Apr 2030system8.21.016cli fpm cgiionCube BoltEnd Of Life, 2025-12-31Ready
8.23.5.x, LTS, 08 Apr 2030system8.21.016cli fpm cgiionCube Boltsecurity, 2026-12Ready
8.33.5.x, LTS, 08 Apr 2030system8.21.016cli fpm cgiionCube Boltsecurity, 2027-12Ready
8.43.5.x, LTS, 08 Apr 2030system8.21.016cli fpm cgiionCube Boltbugfix, 2028-12Ready
8.53.5.x, LTS, 08 Apr 2030system8.21.016cli fpm cgiionCube Boltbugfix, 2029-12Ready

Only supported versions will be automaticly updated at CI.

⁠Notes on the matrix
  • OpenSSL 1.1.x support landed in PHP 7.1. Everything below that is pinned to 1.0.2u. PHP 7.0 builds fine against 1.1.1w in practice, so it uses that.
  • OpenSSL 3.x is only reliable from PHP 8.1. PHP 8.0 stays on 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.
  • All builds are NTS (non-thread-safe), non-debug. This is dictated by the proprietary loaders: they ship one blob per 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.
  • ionCube skipped PHP 8.0 entirely — the vendor treated it as a transitional release after 7.4 and resumed with 8.1. There is no 8.0 loader and there never will be. phpBolt covers 7.0–8.5 and is unaffected.
  • Branches on 1.0.2u are limited to TLS 1.2 — TLS 1.3 does not exist in that OpenSSL branch.

⁠Baked dependencies

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
ICUPHPWhy this boundary
system 768.1–8.5ext/intl compiles as C++17 from 8.1; ICU 74+ headers require C++17
73.27.4, 8.0last branch whose headers stay C++11-clean
67.15.x, 7.0–7.3ICU 68 changed operator== return type UBool → bool
  • ICU 68 changed BreakIterator::operator== to return bool. PHP ≤ 7.3 overrides it with UBool, which C++ rejects as a conflicting return type.
  • ICU 74/75 moved to C++17 in public headers. ext/intl uses -std=c++11 until PHP 8.1.
  • ICU 65 disabled the implicit using namespace icu;. PHP 7.0 relies on it, hence -DU_USING_ICU_NAMESPACE=1 in CXXFLAGS for that branch only.
  • PHP 7.0 detects ICU through icu-config, not pkg-config — ICU_CFLAGS / ICU_LIBS are ignored there. Use --with-icu-dir=${ICU_67_DIR} instead.
  • The ICU .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"
    
  • The system ICU must be hidden from both the compiler and the linker inside the builder stage: headers (/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
curlOpenSSLPHP
8.21.03.58.1–8.5
8.17.01.1.17.0–8.0
8.17.01.0.25.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.
  • Built without 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
libpqOpenSSLPHP
163.5 / 1.1.17.0–8.5
131.0.25.x
  • Only 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.
  • Unlike Debian's libpq, ours links neither LDAP nor Kerberos — gssencmode and LDAP lookups are unavailable.
  • PostgreSQL 16 does build against OpenSSL 1.0.2, but that combination is untested upstream; branch 13 is used there instead as a contemporary of that OpenSSL.
⁠libzip + libmemcached (7.0–8.0)
LibraryVersionOpenSSLPHPPrefix
libzip1.11.41.1.17.0–8.0${EMG_LOCAL_BASE_DIR}/libzip/1.11.4/openssl111
libmemcached1.1.4none7.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:

  • libzip against our OpenSSL 1.1.1 (RPATH to it) — ZipArchive encryption keeps working. libzip-dev is not installed.
  • libmemcached is the maintained awesomized⁠ fork (what Debian ships too), built without SASL (the system 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.

⁠Compilers

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.

⁠Security patches on end-of-life branches

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.

PHPReleasePatchesphp-src source
7.47.4.33CVE-2026-109618, CVE-2026-109619 (advisory 2026-10-21⁠)PHP-7.4-CVE-2026-109618-109619, commit 712e60f9edfd
8.08.0.30CVE-2026-109618, CVE-2026-109619PHP-8.0-CVE-2026-109618-109619, commit 82f8f31b16a0
8.18.1.34CVE-2026-109618, CVE-2026-109619PHP-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.

⁠PEAR / PECL

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:

PHPBundled PEARWhat the image does
5.3, 5.41.9.xpecl 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.51.10.1PEAR 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.11.10.7 - 1.10.16the same with Archive_Tar 1.6.0
8.2 - 8.51.10.18as shipped

The image's pear / pecl therefore work against pecl.php.net for later pecl install too.

⁠Per-branch build workarounds

Applied inside the builder stage. Each one is a symptom of Trixie being far newer than the PHP branch being built.

WorkaroundBranchesReason
ac_cv_func_readdir_r=no≤ 7.3Modern glibc declares readdir_r via __REDIRECT; PHP's autoconf test misdetects it and emits a two-argument call
freetype-config shim≤ 7.3Debian dropped freetype-config; PHP's gd detection still calls it
sed patch on main/php_config.h7.0, 7.1zend_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.2fopencookie seeker signature mismatch; gcc 14 turned this into an error
-fpermissive in CXXFLAGS≤ 7.1C++ strictness in ext/intl
-DU_USING_ICU_NAMESPACE=17.0ICU 65 disabled the implicit using namespace icu;
--with-icu-dir=7.0ext/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.

⁠PHP module API versions

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}/
PHPAPI
5.520121212
5.620131226
7.020151012
7.120160303
7.220170718
7.320180731
7.420190902
8.020200930
8.120210902
8.220220829
8.320230831
8.420240924
8.520250925

⁠Bundled extensions

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.

⁠PECL extensions

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.

Extension8.58.1–8.47.2–8.07.0–7.1Priority
igbinary3.2.17RC13.2.163.2.163.2.1615
msgpack3.0.13.0.12.2.02.2.015
apcu5.1.285.1.285.1.245.1.2420
redis6.3.06.3.06.0.25.3.720
memcached3.4.03.4.03.2.03.2.020
imagick3.8.13.8.13.7.03.7.020
timezonedb2026.32026.32026.32026.330
  • 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.
  • Build order matters. 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).
  • PECL is deprecated upstream in favour of PIE⁠. PIE requires PHP 8.1+ and does not yet cover every extension here, so PECL remains the primary mechanism. pie is installed on 8.1+ for forward compatibility.
⁠Extensions the Debian / sury packages have (ADR-067)

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.

ExtensionDefault8.58.1–8.48.07.3–7.47.1–7.27.05.3–5.6
yamlon2.3.02.3.02.3.02.3.02.3.02.0.41.3.2
mailparseon3.2.03.2.03.2.03.2.03.1.33.1.32.1.6
memcacheongit ac8e8c5*8.28.24.0.5.24.0.5.24.0.5.22.2.7
gnupgon1.5.41.5.41.5.41.5.41.5.41.5.41.5.4
gearmanon2.2.12.2.12.2.12.2.12.1.4 / 2.2.12.1.31.1.2
opentelemetryoff1.4.21.4.21.1.3----
xmlrpcoffPECL 1.0.0RC3PECL 1.0.0RC3PECL 1.0.0RC3corecorecorecore
pspelloffPECL 1.0.1core (8.4: PECL 1.0.1)corecorecorecorecore
enchantoffcorecorecore----
ldap (+SASL)offcorecorecorecorecorecorecore
ssh2off1.5.01.5.01.5.01.4.11.4.11.4.10.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).

⁠Proprietary loaders

Vendor binaries, no source available.

LoaderLoad typePriorityPHP rangeSource
ionCubezend_extension054.1 – 8.5, no 8.0downloaded from ioncube.com at build
phpBoltextension067.0 – 8.5shipped in-repo under usr/local/lib/php/
perforceextension207.0 – 8.5downloaded 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.
  • Always use the non-_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.
⁠Perforce releases

The p4php blob lives in a different Perforce release per PHP branch, and the OpenSSL variant must match the branch:

PHPReleaseBlob
7.0–7.1r20.1perforce_php7N-ssl1.1.1.so
7.2–8.0r24.1perforce_php7N-ssl1.1.1.so
8.1r25.2perforce_php81-ssl3.so
8.2–8.5r26.1perforce_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.

⁠Layout

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.

⁠Load priorities
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

⁠Helper scripts

Derived from docker-library/php⁠ (MIT) and Debian php-common conventions.

ScriptPurpose
php-ext-installbuild + install + enable a bundled extension from source
php-ext-configurephpize + ./configure for a bundled extension
php-ext-enablewrite mods-available/<mod>.ini for a built .so and enable it
php-enmodlink mods-available/<mod>.ini into conf.d/NN-<mod>.ini
php-dismodremove 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.

⁠php-fpm defaults

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 reach

Truncated - full README: https://github.com/EpicMorg/docker/blob/master/linux/ecosystem/apps/php/README.md⁠

Tag summary

Content type

Image

Digest

sha256:67d2481dc…

Size

1 GB

Last updated

4 days ago

docker pull epicmorg/php:8.0