Sign inSign up

winterhearted/php-fpm

By winterhearted

•Updated about 16 hours ago

php-fpm, batteries included

Image
Languages & frameworks
0

50K+

winterhearted/php-fpm repository overview

⁠php-fpm, batteries included

PHP-FPM with the extensions you'd end up installing anyway, already in. Tune it with env vars instead of baking your own ini files. PHP 5.3 through 8.5.

Built at the byte0.org⁠ lab for internal use at Pantera⁠, a web studio. Public because there's no reason not to be.

⁠Run it

docker run -d -v $(pwd):/var/www/html winterhearted/php-fpm:8.4

Listens on 9000, serves from /var/www/html. Point nginx at it, done.

⁠Tags

8.5 8.4 8.3 8.2 8.1 — pick one.

8.0 7.4 7.3 7.2 7.1 7.0 5.6 5.5 5.4 5.3 — EOL, kept for old projects. The 5.x ones are built from source.

Each has a -latest alias and exact patches too (8.4.23). amd64, arm64, riscv64.

⁠-vips

8.5-vips 8.4-vips 8.3-vips 8.2-vips — same image plus libvips and FFI turned on. Also as 8.4-latest-vips, 8.4.23-vips, etc.

Use it with php-vips⁠ 2.x, which talks to the library over FFI. The old pecl vips extension has been dead since PHP 8.0, so it's not here.

libvips isn't small and most projects don't need it, hence the separate tag — the plain images stay lean.

⁠8.6-rc — experimental

8.6-rc tracks the upstream PHP 8.6 alpha/RC images and is rebuilt weekly. Also 8.6-rc-vips, and dated snapshots like 8.6-rc-20260805 if you want to pin something that won't move under you mid-debug.

For seeing what breaks ahead of the release, nothing more. The feature set isn't frozen, so things will change between rebuilds and some of them will change under you. There's deliberately no 8.6 or 8.6-latest tag — those stay empty until 8.6 actually ships.

One difference worth knowing: imagick is built from its git master here, because the last released version doesn't compile against 8.6 yet. amd64, arm64, riscv64, same as everything else.

⁠Knobs

VarSets
PHP_DATE_TIMEZONEdate.timezone
PHP_MEMORY_LIMITmemory_limit, e.g. 256M
PHP_MAX_EXECUTION_TIMEseconds
PHP_MAX_INPUT_TIMEseconds
PHP_POST_MAX_SIZEe.g. 100M
PHP_UPLOAD_MAX_FILESIZEe.g. 100M
PHP_ENABLE_OPCACHE1/0, CLI included
PHP_ENABLE_JIT1/0, 8.0+
PHP_DISPLAY_ERRORS1/0, 8.2+. 0 keeps errors out of responses and only logs them
PHP_ERROR_LOGerror_log, a file path, 8.2+

Leave one unset and you get PHP's default. Each just drops a file into conf.d/, nothing clever going on.

There's no php.ini in the image, so out of the box errors are printed into the response. FPM always logs them too, to docker logs or to the PHP_ERROR_LOG file if set; PHP_DISPLAY_ERRORS=0 leaves them only in the log. The CLI just prints, so nothing is doubled in stderr. The file's directory must be writable by www-data; if it isn't, PHP falls back to docker logs, so nothing gets lost.

FPM pool, 8.2+:

VarSetsDefault
PHP_FPM_PMpm: dynamic, ondemand, staticdynamic
PHP_FPM_MAX_CHILDRENpm.max_children12
PHP_FPM_START_SERVERSpm.start_servers3
PHP_FPM_MIN_SPARE_SERVERSpm.min_spare_servers2
PHP_FPM_MAX_SPARE_SERVERSpm.max_spare_servers5
PHP_FPM_MAX_REQUESTSpm.max_requests500

Set only PHP_FPM_MAX_CHILDREN and the spare counts scale with it, so PHP_FPM_MAX_CHILDREN=4 just works instead of failing on max_spare_servers > max_children. These go to php-fpm.d/zz-env.conf; a config you mount there under a name that sorts later (zz-mysite.conf) still wins.

⁠Defaults, 8.2+

The official image is tuned for nothing in particular — 5 workers, interrupted requests on reload. These images ship defaults that sit at ~15 MB idle on a laptop and still hold a mid-traffic site: a corporate site during an ad campaign, a local shop during a sale.

  • FPM: 12 workers max, 2–5 kept idle (see the table above), each recycled after 500 requests so extension leaks don't pile up. process_control_timeout = 10s lets in-flight requests finish on a reload (USR2). If 10 workers segfault within a minute, FPM restarts itself. log_errors is on, so errors are logged even when they're displayed.
  • PHP: expose_php = Off. Opcache gets interned_strings_buffer = 16 and max_accelerated_files = 20000; the stock 8 / 10000 runs out on anything with a real vendor/.

They live in conf.d/00-defaults.ini and php-fpm.d/zz-defaults.conf. Env vars and your own files override them.

⁠What's inside

pdo (mysql / pgsql / sqlite), mysqli, gd with jpeg/png/webp/avif/freetype, imagick, opcache, intl, zip, bcmath, exif, soap, mbstring, curl, openssl, sodium, and the usual dom/xml set.

Run php -m if you want the exact list for a given tag.

⁠Heads up

8.2–8.5 are built in two stages: extensions get compiled in a throwaway builder, the final image is plain Debian plus the finished PHP. Much smaller, but no compiler in there — docker-php-ext-install in a derived image won't work. Everything you'd normally add is already baked in; if something's genuinely missing, open an issue.

riscv64 sometimes trails the other arches by a patch. Moving tags like 8.4 don't wait for it — you may get a slightly older build there.

MIT.

Tag summary

Content type

Image

Digest

sha256:26fa43bd8…

Size

147.5 MB

Last updated

about 16 hours ago

docker pull winterhearted/php-fpm:8.5-latest-vips