Skip to content

perf: de-bloat FPM images by dropping build-only packages - #26

Merged
imorland merged 7 commits into
masterfrom
im/fpm-debloat
Nov 3, 2026
Merged

imorland merged 7 commits into
masterfrom
im/fpm-debloat

Conversation

@imorland

@imorland imorland commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Removes build-time leftovers from the php83fpm / php85fpm images. All tools, extensions, ImageMagick/Ghostscript, AWS CLI and the GeoIP databases stay.

Changes

  • Extensions are built in one layer that installs the -dev packages, compiles, and purges them, keeping only the runtime libraries the extensions link against. This is the official php image's apt-mark + ldd technique.
  • The PHP source extracted by the separate gd configure step (145 MB on disk) no longer ships.
  • Runtime packages use --no-install-recommends. Recommends that do real work are named explicitly: openssh-client/less for git, ImageMagick's extra coders and HEIC/AVIF encoders, Ghostscript's CJK fallback font, and the perl modules for mytop/innotop. Dropped: exim4, dbus, systemd extras, netpbm, docs.
  • docker-php-ext-install xml is removed. xml is compiled into the official image, so this step only produced an xml.so that was never loaded.
  • The build fails if PHP prints a startup warning or an expected extension doesn't load.
  • New docker-image-fpm-check.yml builds all FPM images on pull requests without pushing. It smoke-tests extensions, tools and Apache → FPM, and writes sizes against the published tags to the job summary.
  • The publish workflow now also rebuilds when bin/ changes, since pecl-install and fetch-geoip-db are copied into these images.

Measured size (uncompressed, local image store, from the check workflow; published tags vs this PR):

Image latest cli dev cli-dev
php83fpm amd64 1652 → 1298 MB 1681 → 1327 MB 1655 → 1300 MB 1683 → 1329 MB
php83fpm arm64 1667 → 1303 MB 1687 → 1322 MB 1670 → 1305 MB 1689 → 1325 MB
php85fpm amd64 1675 → 1319 MB 1720 → 1364 MB 1678 → 1322 MB 1722 → 1367 MB
php85fpm arm64 1686 → 1320 MB 1719 → 1354 MB 1689 → 1323 MB 1722 → 1356 MB

About 355–366 MB (≈21%) smaller per tag. The compressed pull size was estimated from the published layers at roughly 548 → 460 MB for php85fpm:latest; it wasn't measured, because nothing is pushed.

Behaviour changes

  • Images built on top that compile an extension against a system library must now install its -dev package themselves. This is documented in each README.
  • With no MTA, PHP mail() now fails instead of queueing to a container-local mailbox that was never delivered anywhere.

Build the PHP extensions in a single layer that installs the -dev
packages, compiles, and purges them again, keeping only the runtime
libraries the extensions link against (the official php image's
apt-mark/ldd technique). Previously headers, static libraries and the
extracted PHP source (~145 MB, left behind by a separate gd configure
layer) shipped in every image.

Install runtime packages with --no-install-recommends, naming the
recommends that do real work (openssh-client and less for git,
ImageMagick's extra coders and HEIC/AVIF encoders, Ghostscript's CJK
fallback font, the perl modules mariadb-client's tools need). This drops
exim4, dbus, systemd extras, netpbm and docs.

Drop `docker-php-ext-install xml`: xml is compiled into the official
image, so it only produced an xml.so that was never loaded.

The build now fails if PHP prints a startup warning or an expected
extension does not load.
The FPM workflow only builds on master and pushes straight to the
published tags, so a broken Dockerfile was only found after merge. Add a
pull-request workflow that builds all four FPM images per version and
arch without pushing, checks extensions, tools and Apache -> FPM, and
reports sizes against the published tags in the job summary.

Also rebuild the FPM images when bin/ changes: pecl-install and
fetch-geoip-db are copied into them.
Overlapping runs (push during the scheduled rebuild, a dispatch) raced on
the arch tags, and the merge jobs could build :latest from arch images of
different runs. Queue runs in one concurrency group without cancelling,
since a run cancelled mid-push leaves the arch tags out of step.

The token is only used for checkout, so limit it to contents: read.
dpkg-query --search also prints "diversion by X from/to: <path>" lines.
They reached apt-mark as package names ("diversion", "by", ...), which
failed the build with exit 123 on every arch and version. Drop them, and
split "pkg-a:arch, pkg-b: <path>" lines so every owning package is kept.
Debian's apache2 package installs /var/www/html/index.html, which
DirectoryIndex serves for /, so the check never reached PHP-FPM.
Job summaries are only visible in the web UI, not through the API or
gh run view --log.
@imorland
imorland marked this pull request as ready for review November 3, 2026 08:16
@imorland
imorland merged commit f4c5799 into master Nov 3, 2026
4 checks passed
@imorland
imorland deleted the im/fpm-debloat branch November 3, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant