A Docker image providing PHP-FPM. Compared to other PHP images, this one is tailored to run without root privileges. All processes use an unprivileged user (uid 1000). And much work has been put into providing proper console output and meaningful messages.
$ docker run flownative/phpHere's an example of a Docker Compose configuration using this image as a PHP-FPM container. The configuration should give you an idea of how to integrate the image, but you'll certainly need to provide more code in order to get it running with your specific application.
For a full-working solution tailored to Neos CMS and Neos Flow, please have a look at Local Beach instead.
volumes:
application:
name: app
driver: local
services:
webserver:
image: flownative/nginx:4
ports:
- "8080"
volumes:
- application:/application
environment:
- NGINX_PHP_FPM_HOST=app_php.local_beach
php:
image: flownative/php:8.5
volumes:
- application:/application
environment:
By default, the PHP logs are written to STDOUT / STDERR. That way, you
can follow logs by watching container logs with docker logs or using a
similar mechanism in Kubernetes or your actual platform.
| Variable Name | Type | Default | Description |
|---|---|---|---|
| PHP_BASE_PATH | string | /opt/flownative/php | Base path for PHP (read-only) |
| PHP_DATE_TIMEZONE | string | UTC | Default timezone (doc) |
| PHP_ERROR_REPORTING | string | 2147483647 | PHP error reporting log levels (doc) |
| PHP_DISPLAY_ERRORS | string | off | Display PHP errors (doc) |
| PHP_ERROR_LOG | string | /dev/stderr | Path leading to the file where PHP errors should be logged |
| PHP_FPM_ERROR_LOG_PATH | string | /opt/flownative/log/php-fpm-error.log | Path leading to the file where PHP-FPM errors should be logged |
| PHP_FPM_ACCESS_LOG_PATH | string | /opt/flownative/log/php-fpm-access.log | Path leading to the file where PHP-FPM access should be logged |
| PHP_MEMORY_LIMIT | string | 750M | PHP memory limit (doc) |
| PHP_OPCACHE_PRELOAD | string | Path and filename of a preload script (doc) | |
| PHP_XDEBUG_ENABLE | boolean | false | Enable or disable the Xdebug extension |
| PHP_XDEBUG_MODE | string | develop | Controls which Xdebug features are enabled, for example "develop" or "debug". See Xdebug manual for details |
| PHP_XDEBUG_CONFIG | string | Values assigned to this variable are propagated as XDEBUG_CONFIG. See Xdebug manual for details | |
| PHP_XDEBUG_DISCOVER_CLIENT_HOST | boolean | false | If enabled, Xdebug will first try to connect to the client that made the HTTP request. See Xdebug manual for details |
| PHP_XDEBUG_CLIENT_HOST | string | Configures the IP address or hostname where Xdebug will attempt to connect to when initiating a debugging connection. See Xdebug manual for details | |
| PHP_XDEBUG_MAX_NESTING_LEVEL | integer | 512 | Controls the protection mechanism for infinite recursion protection. See Xdebug manual for details |
| PHP_IGBINARY_ENABLE | boolean | false | Enable or disable the igbinary extension |
| PHP_EXCIMER_ENABLE | boolean | false | Enable or disable the Excimer extension |
| PHP_FPM_USER | string | 1000 | User id for running PHP (read-only) |
| PHP_FPM_GROUP | string | 1000 | Group id for running PHP (read-only) |
| PHP_FPM_PORT | string | 9000 | Port the PHP-FPM process listens to |
| PHP_FPM_MAX_CHILDREN | string | 20 | Maximum number of children to run |
| PHP_FPM_MAX_REQUESTS | string | 500 | Number of requests after which a worker is recycled to mitigate memory leaks; set to 0 to disable |
| PHP_FPM_REQUEST_TERMINATE_TIMEOUT | string | 0 | Time after which a request is forcibly terminated, e.g. "300s"; set to 0 to disable |
| PHP_FPM_REQUEST_SLOWLOG_TIMEOUT | string | 0 | Log requests (and count them on the FPM status page) that take longer than this, e.g. "5s"; set to 0 to disable |
| PHP_FPM_SLOWLOG_PATH | string | /opt/flownative/log/php-fpm-slow.log | Destination file for slowlog backtraces, used when PHP_FPM_REQUEST_SLOWLOG_TIMEOUT is enabled |
| PHP_FPM_PM_MODE | string | ondemand | Process manager mode for PHP-FPM; "static", "ondemand" or "dynamic" |
| PHP_SPX_ENABLE | boolean | false | Enable or disable the SPX extension |
| PHP_SPX_KEY | string | dev | The secret key used for authentication |
| PHP_SPX_IP_WHITELIST | string | The IP address white list used for authentication as a comma separated list of IP addresses† | |
| PHP_SPX_IP_VAR | string | REMOTE_ADDR | The $_SERVER key holding the client IP address used for authentication |
| PHP_SPX_TRUSTED_PROXIES | string | 127.0.0.1 | The trusted proxy list as a comma separated list of IP addresses†, ingored when PHP_SPX_IP_VARis REMOTE_ADDR |
† – * (match all) and subnet masks (e.g. 192.168.1.0/24) are supported.
This image is designed to run as a non-root container. Using an
unprivileged user generally improves the security of an image, but may
have a few side-effects, especially when you try to debug something by
logging in to the container using docker exec.
When you are running this image with Docker or in a Kubernetes context, you can take advantage of the non-root approach by disallowing privilege escalation:
$ docker run flownative/php:8.5 --security-opt=no-new-privilegesWhen you exec into this container running bash, you will notice your prompt claiming "I have no name!". That's nothing to worry about: The container runs as a user with uid 1000, but in fact that user does not even exist.
$ docker run -ti --name php --rm flownative/php:8.5 bash
I have no name!@5a0adf17e426:/$ whoami
whoami: cannot find name for user ID 1000
Build this image with docker build. You need to specify the desired
version for some of the tools as build arguments:
docker build \
--build-arg PHP_VERSION=8.5.0 \
-t flownative/php:latest .Check the latest stable release on php.net.
The workflow .github/workflows/docker.test.yaml builds the image for
every supported PHP version on each push and pull request and runs
tests/image-formats.php inside it. The script checks that GD, Imagick
and vips can read, manipulate and write JPEG, PNG, GIF, WebP, AVIF and
HEIC images, can read SVG, TIFF and the first page of a PDF, keep
transparency when writing PNG, GIF, WebP, AVIF and HEIC, and keep the frames of
an animated GIF when writing GIF and WebP. A library without any API for
a format, such as GD built without AVIF support, is reported as skipped.
Any other failure, or a format which no library can read or write, fails
the run. The results appear in the job summary and the generated images
are uploaded as a build artifact.
To run the test against a local image:
docker run --rm \
--volume "$(pwd)/tests:/tests:ro" \
flownative/php:8.5 \
php /tests/image-formats.phpPass --output-dir=/some/path (a writable volume) to keep the generated
images and a Markdown summary of the results.
As of October 2026 the test reports these gaps:
- AVIF can be read but not written. ImageMagick and libvips both rely on
libheif, which is installed with decoder plugins only. Installing
libheif-plugin-aomencas a runtime package of the imagick or vips extension gives both an AV1 encoder. GD would additionally needlibavif-devat build time and--with-avifin the configure call. - Imagick reads SVG through the coder from
libmagickcore-7.q16-10-extra, which Debian builds without librsvg. libvips renders SVG through librsvg. - Imagick cannot read PDF because Ghostscript is not installed, although the ImageMagick policy explicitly allows the PDF coder. The check is reported as skipped in that case; images built on top of this one, such as docker-beach-php, install Ghostscript. libvips renders PDF pages through poppler.
- GD has no API for SVG, HEIC, TIFF or PDF and reads only the first frame of an animated GIF. Imagick and libvips handle all of these, including writing animated GIF and WebP.
- GD writes a transparent GIF only for the colour declared with
imagecolortransparent(), andimagewebp()rejects palette images, so a transparent palette PNG has to go throughimagepalettetotruecolor()before it can be converted to WebP. - The imagick extension reports
@PACKAGE_VERSION@as its version because it is built from a GitHub source archive instead of the PECL release.
The Flownative images are built through Github Workflows. A new release build is triggered whenever a new Git tag is pushed to this repository. As usual, the tag must follow the syntax "v1.2.3+4". Note that the tag is used for the code of this repository and has nothing to do with an actual PHP version.
In order to produce images for a new PHP version, update the
PHP_VERSION constants found in .github/workflows/docker.build.yaml,
commit the result and push it along with a new tag. Note that, because
you did not fix a bug or add a feature of the actual image code, you
should only raise the build version number (ie. v1.2.0+1 becomes
v1.2.0+2). The Github workflow will build images for all supported PHP
branches, tag them and push them to the container registries at Github,
Google and Docker Hub.
Each night, the images for the most recent version of each branch will
be rebuilt using the latest Flownative base image (flownative/base).
Whenever new images were built, a custom event is triggered which in
turn triggers builds of the flownative/beach-php image, which is
derived from this one.
