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"]