Skip to content

PIE 1.5 in Docker and CI: What Replaces pecl install

PIE 1.5 in Docker and CI: What Replaces pecl install

I have a Dockerfile in my head that I’ve copy-pasted into maybe a dozen client projects. Three lines of docker-php-ext-install, one pecl install redis, one docker-php-ext-enable, and a prayer that nobody asks me why the Redis line works but the intl line needs libicu first. It works. It has always worked. So when PIE 1.5 landed in September and Laravel News later ran a piece saying CI installs now need explicit package choices, my first reaction was a long sigh.

Then I read the release notes and the usage docs, and the sigh got shorter. I haven’t migrated a client image to PIE yet, so this isn’t a war story. It’s what I’d check before I touch a working Dockerfile, plus one security fix you should know about this week.

What PIE is replacing

PIE is the PHP Installer for Extensions, maintained under the PHP Foundation, and it’s the replacement for PECL. If you’ve never used PECL directly, you’ve probably still used it through pecl install redis or pecl install xdebug in a Dockerfile. The usage docs include a PECL comparison table, and the short version is that PIE resolves packages through Packagist instead of the old PECL channel. An extension author publishes a Composer-style package, and PIE downloads, builds and enables it.

That sounds like a small change until you remember where PECL packages come from. Half the extensions I install for clients (redis, xdebug, imagick, sometimes swoole) are only reachable through that channel, and the channel has been showing its age for years.

One distinction trips people up, so let me be careful with it. docker-php-ext-install, as the official PHP image docs describe it, builds extensions that ship inside the PHP source tree: pdo_mysql, gd, intl, bcmath and friends. PIE doesn’t replace that script for those. PIE replaces the PECL half of the story, the extensions that live outside php-src. If your Dockerfile only installs bundled extensions, nothing about PIE 1.5 forces your hand.

The 1.5 change that breaks CI

Here’s the part the Laravel News post is about. PIE can run inside a project directory and look at the ext-* entries in your composer.json. Run plain pie install there, and it checks each required extension, finds the ones that are missing, and offers candidate packages.

Before 1.5, a CI pipeline could pass --allow-non-interactive-project-install and let PIE pick. The docs now say plainly that this flag no longer works. From 1.5 you must name the package for each missing extension with --select:

pie install \
  --select redis=phpredis/phpredis \
  --select example_pie_extension=asgrim/example-pie-extension

I like this change more than I expected to. Letting an installer guess which package provides ext-redis is how supply chain stories begin. Naming phpredis/phpredis in your own Dockerfile is a line a reviewer can read. The cost is one more thing to maintain, and a pipeline that used to be green on a Tuesday will go red after a base image bump pulls in a newer PIE. If you float the PIE version, you’ll find out the hard way.

The same release adds --no-dev, which skips extensions that only appear in require-dev. That’s the xdebug problem. I have seen production images ship with xdebug loaded because the project required it for tests and nobody told the installer otherwise. The docs use exactly that example, so I’m not the only one who has been bitten.

A Dockerfile I would try first

The docs recommend a bind mount from the binary-only PIE image rather than installing a PHAR yourself. The image has no PHP runtime in it, so it’s only a delivery mechanism for the binary. Here’s the shape I’d test, based on their example:

FROM php:8.4-cli

RUN --mount=type=bind,from=ghcr.io/php/pie:1.5-bin,source=/pie,target=/usr/local/bin/pie \
    set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends unzip $PHPIZE_DEPS; \
    pie install --no-cache phpredis/phpredis; \
    apt-get purge -y --auto-remove unzip; \
    rm -rf /var/lib/apt/lists/*

I haven’t built this exact file, so treat it as a starting point and not a recipe. Two details I’d verify. First, $PHPIZE_DEPS is an environment variable the official PHP images set for the compiler toolchain, and I’d confirm it exists in the tag you use. Second, purging the toolchain after the build is a size optimisation that can also remove packages another layer needs, so check with a fresh build.

On build tools, PIE checks for them before it starts. In an interactive terminal it asks whether to install what’s missing. In CI it won’t. The docs say you pass --auto-install-build-tools to skip the prompt, or --no-build-tools-check to skip the check entirely. New in 1.5 is pie check-build-tools, which prints a list of what’s present, so you can run it as its own step and fail early with a readable message instead of a wall of make output.

System libraries get the same treatment, with --auto-install-system-dependencies for non-interactive runs.

The security fix I’d pin for

This is the reason I’d look at your PIE version this week, even if you never touch --select. On October 30 the project shipped 1.5.5 and 1.4.12 to fix GHSA-4538-pqqf-mv67. When make fails with more than 5000 bytes of output, PIE writes that output to a file in the system temp directory and prints the file name. The advisory says the file is created without locking down permissions and without checking that it doesn’t already exist. On a shared machine, another user could read it or plant a file there first.

How much that matters depends on where you run builds. On a single-purpose container, close to nothing. On a shared CI runner, or a dev server where several people have shells, it’s a real if modest hole, and build logs do sometimes contain tokens. I’d treat it as “upgrade when convenient” for containers and “upgrade now” for shared runners. If you use the 1.5-bin image tag, you pick up 1.5.5 on the next pull. If you pinned 1.5.0-bin for reproducibility, that pin is now the problem.

I wrote about a similar “patch now or accept the exposure” call in my PHP 8.5.12 security update checklist, and the reasoning is the same here: work out who can reach the vulnerable thing, then decide how fast to move.

Smaller 1.5 features I’d actually use

The 1.5.0 release notes list 56 merged pull requests from 21 contributors, which is a lot for a point release of an installer. Most of it is edge-case fixes. A few items changed my plans.

pie install --from-lock installs from a pie.lock, which finally gives extensions something close to what composer.lock gives packages. I’d commit that file wherever your PIE config lives. pie upgrade updates every PIE-installed extension within the constraint you originally used, so phpredis/phpredis:^6.3 won’t jump to a hypothetical 7.0 behind your back. You can also install several extensions in one command, and pie search exists now, which sounds trivial until you’re trying to remember whether the Redis package is phpredis/phpredis or something else.

There’s also SBOM attestation, and the docs show gh attestation verify --owner php pie.phar for checking the PHAR. I’d add that line to any pipeline that downloads the PHAR directly. If you use the Docker bind mount, you’re trusting the registry tag instead, so pin by digest if your policy cares.

One removal to flag: the experimental macOS Intel executable builds are gone from 1.5. If a teammate is still on an Intel Mac and runs the binary version locally, they’ll want the PHAR.

What I’m not sure about

I couldn’t read the full Laravel News article body when I put this together, only its summary, so I leaned on the official docs and release notes for every claim above. The summary mentions a Laravel project’s Redis install and skipping development extensions, which matches the --select and --no-dev sections in the docs.

What I haven’t tested is how PIE behaves on Alpine-based images, where the toolchain package names differ, or how long an extension compile adds to a cold CI cache. Both matter more to me than the CLI flags do. I’m also not convinced PIE beats the old pecl install line for a project with exactly one extension and no plans to grow. The new flags are better. They’re also more to read.

What to do this week

Run grep -R "pecl install\|allow-non-interactive" . across your Dockerfiles and CI configs and see what turns up. If the second pattern matches anything, that pipeline will break the day you move to PIE 1.5, and it’s better to learn that in a branch. Then check which PIE version your images pull, and move anything below 1.5.5 or 1.4.12 forward.

If you’d like a second pair of eyes on a PHP deployment pipeline that has grown barnacles, that’s the kind of cleanup I do in my client work.