Skip to content
lerd-envPublic

About

Static PHP builds for lerd's native runtime on macOS

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Lerd PHP Binaries

The PHP builds that power Lerd's native runtime on macOS and Windows — run PHP on the host instead of in a container, no image required.

Part of Lerd Docs License: MIT

On macOS your project lives on the host and is mounted into the Podman VM, so a containerised PHP crosses that boundary for every file it reads. Measured over 2000 files in one PHP process, a stat costs 108ms in a container against 4ms on the host, and an include 320ms against 31ms. Lerd's native runtime removes the boundary by running PHP-FPM, the CLI and the workers directly on the host, and this repo is where those binaries come from.

The build carries the same extension set the container image does, so Xdebug, SPX, dumps and the debug bridge keep working. Windows builds are assembled differently and lack a few Unix-only extensions; see Windows.

What a build produces

A single version build outputs:

  • 🐘 php-native-<version> — the CLI, behind lerd php, composer, tinker and every worker
  • ⚡ php-native-fpm-<version> — the FPM nginx fastcgi's to from inside the VM
  • 🧩 modules/*.so — the extensions that can only exist as shared objects, Xdebug and pcov among them
  • 🔐 A sha256 — Lerd verifies every download against the digest pinned in its manifest
  • 🏷️ Built by lerd — what php -v reports, so a binary always says where it came from
  • 🧾 BUILD-INFO.txt — the static-php-cli release and digest that produced it
  • 📄 THIRD-PARTY-NOTICES.txt — PHP's licence and every statically linked library's, as those licences require of a binary distribution

Builds cover Apple silicon and Intel. GitHub retired the macos-13 image in December 2025; Intel builds use its replacement, macos-15-intel, which is the last x86_64 image and retires in August 2027. Intel will need another home before then.

Windows

Windows reaches the project over a 9p share, which makes the container boundary far more expensive than on macOS: over the same 2000 files, stat costs about 2070ms in a container against 46ms on the host, and include 5300ms against 198ms.

Windows has no PHP-FPM, and static-php-cli builds cannot load DLL extensions, so the Windows build is assembled rather than compiled. scripts/build-windows.py takes the official windows.php.net NTS build, verified against the digest it publishes, adds the PECL and Xdebug DLLs pinned in windows-extensions.txt, and compiles only lerd_devtools, against the matching devel pack. php-cgi.exe serves in place of FPM, with PHP_FCGI_CHILDREN giving it a pool whose children share one OPcache.

A Windows asset (lerd-php-<patch>-windows-x86_64.tar.gz, platform windows/amd64) unpacks to:

  • 🐘 php-native-<minor>/ — the official build as a directory, since php.exe and php-cgi.exe need php8.dll and the DLLs beside them; ImageMagick's DLLs sit here too, where Windows looks for them, and so does the Visual C++ runtime everything shipped imports (vcruntime140.dll, vcruntime140_1.dll, msvcp140.dll, vcomp140.dll), so PHP runs on a machine without the redistributable
  • 🧩 modules/ — what loads on demand: xdebug.dll, pcov.dll, lerd_devtools-<minor>.dll
  • ⚙️ conf.d/10-lerd-extensions.ini — turns on the extensions the macOS build compiles in; extension_dir is left for lerd to set, since only it knows where the tree was unpacked
  • 🧾 BUILD-INFO.txt and THIRD-PARTY-NOTICES.txt — every source with its version and sha256, and every licence shipped with them

The pins and the manifest are shared with macOS: a minor carries one patch on every platform. windows.php.net publishes its build hours after php.net announces a patch, so a run that catches the gap publishes macOS alone, and the next run adds the Windows asset to the same release. Until it does, the manifest leaves Windows out of that minor rather than holding macOS back.

pcntl, posix, sysvmsg, sysvsem and spx do not exist on Windows; windows-unavailable.txt lists them and every build records the gap in BUILD-INFO.txt. Lerd profiles with Xdebug there instead of SPX.

A module only loads into a PHP linked with the same or a newer MSVC, so the collector for 8.1 to 8.3, whose cores were linked with VS 2019, is built with the VS 2019 toolset; build-windows.py picks the right one from the core's PE header.

Available versions

PHP Native runtime Notes
8.5 ✅
8.4 ✅
8.3 ✅
8.2 ✅
8.1 ✅
8.6 ⏳ prerelease; blocked on static-php-cli, whose phpmicro patches do not apply to 8.6.0beta2. The scaffolding is in place, see prerelease.txt
8.0 ❌ fails against current libxml2 and ICU; builds only with the XML extensions stripped, which no framework survives
7.4 ❌ static PHP carries no OPcache below 8.0, and the same libxml2 and ICU walls apply

Lerd refuses to switch an install to the native runtime while any site runs a version that is not here, and names the sites standing in the way.

Prereleases

A PHP with no GA release is built from a pinned source tarball listed in prerelease.txt, with the extensions that do not compile against it dropped via unbuildable-prerelease.txt. That list mirrors lerd's own prereleaseUnbuildable, so a prerelease advertises the same extensions whichever runtime serves it.

Extensions

extensions.txt is the static set compiled into the binary, and shared-extensions.txt those that can only be built as loadable objects. Together they match what the PHP image ships, including intl, imagick, mongodb, redis, soap, xsl and spx.

ldap is currently absent; see known issues.

The set is fixed at build time, so lerd php:ext and lerd php:pkg refuse under the native runtime. A project needing something outside it stays on container mode, and Lerd's site doctor reports the drift before it surfaces at runtime.

Usage

Lerd downloads the build matching a project's PHP version the first time it needs one, verifies the digest, and puts the binaries in ~/.local/share/lerd/bin with their extensions in ~/.local/share/lerd/native-php/<version>/modules:

lerd php:runtime native      # move PHP onto the host
lerd php:runtime container   # back to the containers
lerd php:runtime             # show the current runtime
lerd php:list                # the versions installed for the active runtime
lerd php:update              # fetch the newest published patch

lerd php:update re-reads the pins, downloads anything newer and restarts the pools running it.

How builds are triggered

Nobody tags a release here. A weekly job asks php.net for the newest patch of every minor in versions.txt, skips the ones already published, and builds the rest. A week where PHP ships nothing costs one short Linux job.

Each patch gets its own release, tagged for the PHP it carries (php-8.4.24), so a download URL names exactly which build it points at and never changes underneath a pinned digest.

The current pin for every minor lives in native-php.yaml on main, regenerated from pins/ after each build. It is rebuilt from every pin on record rather than from the run that produced it, so rebuilding one minor does not drop the other four. Lerd reads that file directly, which is how a new patch reaches installs without a lerd release.

workflow_dispatch runs the same thing by hand, optionally for a single minor, Apple silicon only, or forcing a rebuild of a patch already published.

The same binaries back NativePHP Jump, whose websocket bridge needs posix and pcntl that NativePHP's bundled PHP does not carry.

Built with static-php-cli

Every binary here is produced by static-php-cli (MIT), and this repository is the thin layer that drives it: which extensions to compile, how to name and package the result, and which licences to ship alongside it. The compiler toolchain, the patch sets, the dependency graph and the library versions are all its work, not ours.

That dependency shapes what is possible here, so it is worth being explicit about:

  • Extension names are spc's, not PHP's. mbregex is a separate entry from mbstring, for instance, and a list written from php -m output silently loses it.
  • spc chooses the library versions. PHP 7.4 and 8.0 fail against the libxml2 and ICU it builds, which is why they are absent above.
  • spc patches phpmicro unconditionally, even for a build that produces only CLI and FPM, which is what currently blocks 8.6.
  • The build provider string is set on spc's own configure line, so Built by lerd is achieved by patching configure.ac in the source tarball before spc extracts it.

Builds track the newest stable static-php-cli release, resolved at build time rather than pinned, so PHP support and fixes arrive without a bump here. Nightly is deliberately avoided: it changes the build tooling underneath a release with no way to tell afterwards. The version and digest that produced a given binary are recorded in BUILD-INFO.txt and shipped inside its tarball, so any artifact can be traced back to its toolchain.

If you are packaging static PHP for your own project, use static-php-cli directly. This repo exists for the parts specific to lerd.

Building locally

scripts/build.sh 8.4 out/      # fetches static-php-cli itself
scripts/package.sh 8.4 out/ dist/

A cold build takes about fourteen minutes and needs roughly 6 GB of scratch. The libraries dominate that and are shared across versions, so each additional version costs about three minutes once buildroot/ and downloads/ are warm.

scripts/build.sh refuses to publish a binary missing dom, simplexml, xml, intl, mbstring or opcache. That guard is why 7.4 and 8.0 are not here: both can be made to compile, and neither can run a real application afterwards.

On Windows, with Python, git and the VS 2022 Build Tools (C++ workload):

$env:LERD_DEVTOOLS_SRC = "C:\path\to\lerd\internal\podman\devtools"   # optional
python scripts/build-windows.py 8.4 out 8.4.26
python scripts/package-windows.py 8.4 out dist 8.4.26

Nothing is compiled but the collector, so a build takes a couple of minutes and about 400 MB of scratch, most of it ImageMagick. 8.1 to 8.3 also need the VS 2019 toolset, component Microsoft.VisualStudio.Component.VC.14.29.16.11.x86.x64. build-windows.py holds the result to the same dom, simplexml, xml, intl, mbstring and opcache guard, refuses any extension in extensions.txt it cannot provide, and loads every DLL it ships before packaging it.

License

The scripts and manifests in this repository are MIT, as is static-php-cli, the tool that builds the binaries.

The binaries they produce are not: PHP is distributed under the PHP License 3.01, and the build statically links OpenSSL, ICU, ImageMagick, libxml2, curl, libsodium, the PostgreSQL client and others, each under its own terms. Every release tarball ships a THIRD-PARTY-NOTICES.txt reproducing those licences in full, which is what those licences require of anyone redistributing a binary.

About

Static PHP builds for lerd's native runtime on macOS

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages