Coming from a more standard LAMP setup, a containerised Dockerised Caddy reverse-proxy with PHP FastCGI (PHP-FPM) is not as straightforward as you may expect!
Note: I'll reference "Docker" here but this should all work for the arguably better (more linuxy) "Podman" too.
The official PHP-FPM container does not log errors to the stdout (i.e. the terminal logs when running via docker-compose). This is not the standard for containers or ideal for containerised development or deployment.
The docs and blogs around the internet on this subject are almost all outdated, and so there's not a super clear solution. There are many parts to the php-fpm container config and most have been resolved in recent container image's apart from this last bit.
So, in 2025-11, there's three parts to get PHP error logs displaying in stdout; the config files which are mounted in via docker compose, the PHP ini config and then the fpm www config.
Project File Structure:
./my-php-app/container-stack/compose.yml./my-php-app/container-stack/conf/Caddyfile./my-php-app/container-stack/php-dev.ini./my-php-app/container-stack/fpm-www.conf
I use some relative paths below, and they expect this config to be in a sub-directory of your PHP projects root.
Docker Compose:
name: php-dev-server
services:
caddy:
container_name: caddy
image: docker.io/caddy:latest
volumes:
- ../:/srv
- ./conf:/etc/caddy
ports:
- 8080:8080
php:
container_name: php
image: php:8.5-fpm
volumes:
- ../:/srv
- ./php-dev.ini:/usr/local/etc/php/conf.d/php-custom.ini
- ./fpm-www.conf:/usr/local/etc/php-fpm.d/zz-php-error-fix.conf
#ports:
# - 9000:9000
- We specify the full URL to pull the caddy image form Docker Hub, to ensure podman compatibility which prefers full links.
- Docker doesn't mount files well when compared to how it mounts directories. It's something to do with change detection. We need to avoid that with the Caddy mounts, but it doesn't seem to be an issue on the PHP mounts.
- Notice that the "php-fpm" (FastCGI) ports are not exposed/forwarded directly. Exposing these ports are not needed since Caddy talks to php-fpm on the internal network made by the compose file. Exposing the ports here would tell Docker (but not Podman) to also expose though the servers' firewall to the internet.
- The "zz" on the php-fpm is to ensure our small config is appended last to overwrite the existing values.
- It's important that the mount point for "../" (i.e. one directory up from this directory) is mounted at the same place inside both Caddy and PHP-FPM for them to work.
Caddy config Caddyfile:
http://*:8080 {
root * /srv
# we specify http only above so if you want internal https, edit that too.
tls internal
# Enable the static file server.
file_server
# Or serve a PHP site through php-fpm:
php_fastcgi php:9000
}
- This will run a local PHP server on http://localhost:8080/ (not HTTPS).
- Notice that the php_fastcgi URL is the internal url within the docker compose network, and it's internal port.
- "file_server" is needed assuming you have static assets as well as php files.
PHP-FPM config php-dev.ini:
short_open_tag = On
display_errors = On
html_errors = Off
- "Short open tags" is optional here, it was just needed for my project.
PHP-FPM config fpm-www.conf:
[www]
php_admin_flag[log_errors] = on
- This is the magic sauce that seems to be missing from the standard container configuration in 2025. It tells the various php-fpm processing to send logs back up the stack to where the other log configs can kick and in direct them to stdout.
Comments & Questions
Reply by email to send in your thoughts.
Comments may be featured here unless you say otherwise. You can encrypt emails with PGP too, learn more about my email replies here.
PGP: 9ba2c5570aec2933970053e7967775cb1020ef23