Images

php-fpm

PHP-FPM without a web server, for nginx or Caddy in another container, a Compose service or a Kubernetes sidecar.

rollogi/php-fpm:{version} is the php image plus PHP-FPM and nothing else. Use it when the web server runs in its own container: an nginx or Caddy service in Docker Compose, a sidecar in the same Kubernetes pod, or an ingress that speaks FastCGI. If you want one container that does both, use php-nginx.

Quick start

The web server and PHP-FPM must see your application at the same path, because the web server sends PHP-FPM the file name to run. Both containers below mount it at /var/www/html.

compose.yaml
services:
  php:
    image: rollogi/php-fpm:8.4
    volumes:
      - ./:/var/www/html:ro

  web:
    image: nginx:1.30-alpine
    ports:
      - "8080:80"
    volumes:
      - ./:/var/www/html:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
nginx.conf
server {
    listen 80;
    root /var/www/html/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

In Kubernetes the same applies inside one pod: nginx and php-fpm share a volume with the code, and nginx passes to 127.0.0.1:9000.

What you get

  • PHP-FPM on port 9000 on every interface, so a web server in another container can reach it. EXPOSE 9000 is declared; never publish the port to the internet. FastCGI has no authentication.
  • The same configuration surface as php-nginx. Every PHP_* variable, php.<setting>, fpm.pool.<setting> and fpm.global.<setting> works, and the pool is rendered from the same template, so moving between the two images changes nothing about PHP.
  • A FastCGI healthcheck. The image's HEALTHCHECK runs php-fpm-healthcheck, which asks the pool's /fpm-ping path over FastCGI and expects pong. It follows PHP_FPM_LISTEN, including a unix socket.
  • Pool status at /fpm-status for a FastCGI exporter such as php-fpm_exporter.
  • Optional client allowlist. PHP_FPM_LISTEN_ALLOWED_CLIENTS sets listen.allowed_clients. 127.0.0.1 is always added so the healthcheck keeps working.
  • Startup commands and cron as in the other images: APP_INSTALL runs in /var/www/html before PHP-FPM starts, and CRON_SCHEDULE with CRON_COMMAND, or a crontab at CRONTAB_FILE, runs alongside PHP-FPM through supercronic.
  • Tagged logs for [php-fpm], [entrypoint], [install] and [cron].

Variables

The PHP-FPM variables are shared with php-nginx. Two defaults differ here:

VariableDefaultDescription
PHP_FPM_LISTEN0.0.0.0:9000Listen address. A path such as /run/php/php-fpm.sock listens on a unix socket in a shared volume instead.
PHP_FPM_LISTEN_ALLOWED_CLIENTS(any)Comma-separated addresses allowed to connect, for example 10.0.0.5,10.0.0.6.
APP_INSTALL_DIR/var/www/htmlWhere APP_INSTALL runs.

PHP_SESSION_COOKIE_SECURE defaults to Off because TLS terminates in the web server container. Set it to On when the site is served over HTTPS.

Users and read-only root

php-fpm runs as UID 1000 by default and works under any --user. In root mode the master runs as root and the pool workers drop to nobody. The read-only recipe needs tmpfs mounts on /run, /tmp and /var/log:

docker run --rm --read-only \
  --tmpfs /run:uid=1000,gid=1000 --tmpfs /tmp --tmpfs /var/log:uid=1000,gid=1000 \
  --cap-drop ALL --security-opt no-new-privileges \
  -v $(pwd):/var/www/html:ro rollogi/php-fpm:8.4

Running other commands

serve is the default command. Anything else runs instead of PHP-FPM, with the configuration rendered first, so the same image works for one-off tasks:

docker run --rm -v $(pwd):/var/www/html -w /var/www/html rollogi/php-fpm:8.4 php artisan migrate --force