thecodingmachine/php v5 documentation GitHub

Variants: cli, apache and fpm

Variant Default command Working directory Port
cli php -v /usr/src/app -
apache apache2-foreground /var/www/html 80 (HTTP)
fpm php-fpm /var/www/html 9000 (FastCGI, no HTTP), 80 (HTTP) with the built-in Apache

Mount or copy your application in the working directory.

cli

The cli images expect a command: override the Docker command, never the entrypoint (it configures PHP, the user, the cron jobs… see Processes).

FROM thecodingmachine/php:8.4-v5-cli

CMD ["php", "myprogram.php", "some_param"]
services:
  my_app:
    image: thecodingmachine/php:8.4-v5-cli
    command: php myprogram.php some_param

apache

Apache runs PHP with mod_php. The sections below also apply to the built-in Apache of the fpm variant.

To serve a new application, prefer the fpm variant with its built-in Apache: the same features, with 63% more traffic served with HTTPS and up to 29% more during a traffic peak (benchmark). See Serving with the fpm variant and its migration notes.

Document root

APACHE_DOCUMENT_ROOT sets the document root (your “public” directory). A relative path is relative to /var/www/html:

# These 2 variables are identical
APACHE_DOCUMENT_ROOT=public/
APACHE_DOCUMENT_ROOT=/var/www/html/public

Ports

Apache listens on port 80 (and 443 when the ssl module is enabled). APACHE_HTTP_PORT and APACHE_HTTPS_PORT change these ports, for instance to use unprivileged ports:

APACHE_HTTP_PORT=8080
APACHE_HTTPS_PORT=8443

Apache modules

Use the APACHE_EXTENSION_<module name> environment variables:

services:
  my_app:
    image: thecodingmachine/php:8.4-v5-apache
    environment:
      APACHE_EXTENSION_DAV: 1
      APACHE_EXTENSION_SSL: 1

As an alternative, the APACHE_EXTENSIONS variable lists the modules to enable:

APACHE_EXTENSIONS="dav ssl"

Modules enabled by default: access_compat alias auth_basic authn_core authn_file authz_core authz_host authz_user autoindex deflate dir env expires filter mime mpm_prefork negotiation php8.x (your PHP version) reqtimeout rewrite setenvif status

For the built-in Apache of the fpm variant, mpm_event proxy proxy_fcgi are enabled instead of mpm_prefork and php8.x (mod_php is not available).

Modules available: access_compat actions alias allowmethods asis auth_basic auth_digest auth_form authn_anon authn_core authn_dbd authn_dbm authn_file authn_socache authnz_fcgi authnz_ldap authz_core authz_dbd authz_dbm authz_groupfile authz_host authz_owner authz_user autoindex brotli buffer cache cache_disk cache_socache cern_meta cgi cgid charset_lite data dav dav_fs dav_lock dbd deflate dialup dir dump_io echo env ext_filter expires file_cache filter headers heartbeat heartmonitor ident imagemap include info lbmethod_bybusyness lbmethod_byrequests lbmethod_bytraffic lbmethod_heartbeat ldap log_debug log_forensic lua macro md mime mime_magic mpm_event mpm_prefork mpm_worker negotiation php8.x proxy proxy_ajp proxy_balancer proxy_connect proxy_express proxy_fcgi proxy_fdpass proxy_ftp proxy_hcheck proxy_html proxy_http proxy_scgi proxy_uwsgi proxy_wstunnel ratelimit reflector remoteip reqtimeout request rewrite sed session session_cookie session_crypto session_dbd setenvif slotmem_plain slotmem_shm socache_dbm socache_memcache socache_redis socache_shmcb speling ssl status substitute suexec unique_id userdir usertrack vhost_alias xml2enc

This list can be outdated, you can verify it with: docker run --rm thecodingmachine/php:8.4-v5-slim-apache a2enmod

Connections and processes

Environment variable apache variant (mpm_prefork) fpm variant (mpm_event) Description
APACHE_MAX_REQUEST_WORKERS 150 150 MaxRequestWorkers: Apache processes at most (each one embedding PHP) or threads (up to 1600)
APACHE_KEEPALIVE On On KeepAlive with the clients
APACHE_KEEPALIVE_TIMEOUT 5 5 KeepAliveTimeout, in seconds

With mod_php, a process is busy for the whole life of a connection, including the idle time of a keep-alive connection, and uses the memory of a PHP request: behind a reverse proxy, turn keep-alive off (APACHE_KEEPALIVE=Off) and size APACHE_MAX_REQUEST_WORKERS for the memory of the container (150 processes of 100 MB need 15 GB).

The tuning simulator chooses these settings (and the PHP-FPM ones) for your application, your users and your container, for instance a CRM with 20 employees or a WordPress site with 100 visitors and HTTPS in the container.

Behind a reverse proxy

Behind Traefik, nginx or a Kubernetes ingress, list the proxies in APACHE_TRUSTED_PROXIES, and set APACHE_TRUSTED_CLOUDFLARE=1 behind Cloudflare: Apache and PHP get the IP of the users (REMOTE_ADDR, access log, Require ip) and HTTPS=on for the requests the proxy received in HTTPS. The container checks the address of each hop itself: a forged X-Forwarded-For or CF-Connecting-IP header is ignored.

services:
  app:
    image: thecodingmachine/php:8.4-v5-fpm
    environment:
      PHP_FPM_WEB_SERVER: apache
      # The name of the Traefik service
      APACHE_TRUSTED_PROXIES: traefik
      # The ranges of Cloudflare (updated every day); "only" also refuses the requests sent without Cloudflare
      APACHE_TRUSTED_CLOUDFLARE: "1"

The reverse proxy guide gives the configuration of your setup (Traefik, nginx, Kubernetes, with or without Cloudflare) and explains why the trusted proxies of Symfony or Laravel are not enough.

Logs

The access log goes to the standard output of the container and the error log to its standard error (docker compose logs app). APACHE_LOG_FORMAT chooses the format of the access log: combined (default), detailed (combined + response time in µs + address of the proxy), json, or a format of mod_log_config written as is.

APACHE_LOG_OUTPUT=file writes them in /var/log/apache2 instead, one file per day (access.2026-11-13.log, error.2026-11-13.log), rotated at midnight UTC, gzipped and deleted after APACHE_LOG_RETENTION_DAYS days (14 by default). APACHE_LOG_MAX_SIZE (100M, 1G) also starts a new file when it reaches this size, the names then getting the time (access.2026-11-13.143002.log). Mount a directory of the server or a volume on /var/log/apache2: without it, the logs are lost with the container.

services:
  app:
    image: thecodingmachine/php:8.4-v5-apache
    environment:
      APACHE_LOG_FORMAT: json
      APACHE_LOG_OUTPUT: file
      APACHE_LOG_RETENTION_DAYS: 30
      APACHE_LOG_MAX_SIZE: 100M
    volumes:
      - ./logs:/var/log/apache2

Apache user

Apache runs as the docker user, which has the rights of the owner of your files (see Permissions). In production, run it as www-data instead:

FROM thecodingmachine/php:8.4-v5-apache

ENV APACHE_RUN_USER=www-data \
    APACHE_RUN_GROUP=www-data

fpm

PHP-FPM listens on port 9000 and speaks FastCGI, not HTTP: put a web server (nginx, Caddy, Apache…) in front of it, in another container, and point it to fpm-container:9000.

$ docker run -p 9000:9000 --rm -v "$PWD":/var/www/html thecodingmachine/php:8.4-v5-fpm

A docker stop stops PHP-FPM gracefully (SIGQUIT), with or without the built-in Apache: the running requests are completed.

Built-in Apache

With PHP_FPM_WEB_SERVER=apache, the fpm variant runs Apache (mpm_event) in front of PHP-FPM, in the same container, with the Apache features of the apache variant (.htaccess, document root, ports, modules). The number of PHP workers is configured independently (see PHP-FPM settings), and APACHE_PROXY_TIMEOUT sets how long Apache waits for PHP-FPM (300 seconds by default).

$ docker run -p 80:80 --rm -e PHP_FPM_WEB_SERVER=apache -v "$PWD":/var/www/html thecodingmachine/php:8.4-v5-fpm

Serving an application with the fpm variant details its advantages, the constraints of PHP-FPM (number of workers and memory, timeouts, php_value in .htaccess), how to use your own web server instead (nginx…) and how to migrate from the apache variant.

PHP-FPM settings

The PHP-FPM process manager can be configured with environment variables (the defaults are the ones of the Ubuntu package):

Environment variable php-fpm.conf setting Default
PHP_FPM_PM pm dynamic
PHP_FPM_PM_MAX_CHILDREN pm.max_children 5
PHP_FPM_PM_START_SERVERS pm.start_servers 2
PHP_FPM_PM_MIN_SPARE_SERVERS pm.min_spare_servers 1
PHP_FPM_PM_MAX_SPARE_SERVERS pm.max_spare_servers 3
PHP_FPM_PM_MAX_REQUESTS pm.max_requests 0
PHP_FPM_ACCESS_LOG access.log /proc/self/fd/2

The php-fpm-healthcheck command checks that PHP-FPM answers (on its /ping endpoint). You can use it as a Docker healthcheck or as a Kubernetes probe:

services:
  my_app:
    image: thecodingmachine/php:8.4-v5-fpm
    healthcheck:
      test: ["CMD", "php-fpm-healthcheck"]