Application Error Logging in Docker
When running non interactive applications in Docker such as a web server or a PHP application (or both!) traditional file-based logging can be real annoying to deal with. Logs written to files inside containers can be lost when containers restart. Those logs are also difficult to aggregate from multiple instances. Generally, a pain.
Many years ago I was managing PHP applications in docker and at that time it was necessary to manually configure logging in PHP-FPM to write to stderr. I have started building and supporting more PHP workloads lately and thankfully the community has mostly resolved the logging issues. I’ve made things more complicated for myself by carrying over configurations from years ago that are no longer necessary or just no longer helpful.
I hope you find this blog post helps you understand the relationship of logging from your application to docker and how docker treats/handles logging.
The short version of all of this is, write your application logs to stderr if you want to access them via docker logs and let your docker logging driver handle them, otherwise they will be lost forever inside your container.
Docker logging.
The docs for docker logging are pretty good. To summarize - docker logs show the commands output just as it would appear if you ran the command interactively in a terminal. Command here means the command your container executes. Linux commands typically open three I/O streams when they run called stdin, stdout, and stderr.
stdoutis usually a command’s normal outputstderris typically used to output error messages.
docker logs shows the commands stdout and stderr.
Something important to know is that docker supports different logging drivers letting you control where to send logs. By default docker uses the json-file driver, which stores the logs in a json file to the hosts file system. Anyway, just know that if your docker configuration deviates from default then docker logs may not show you anything.
Nginx.
If you look at the official nginx docker image on docker hub you will see that it symlinks the access and error log paths to /dev/stdout and /dev/stderr respectively.
RUN set -x \
# a bunch of setup and then...
# forward request and error logs to docker log collector
&& ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log \
If you install nginx in your container and are not using the official image you will want to ensure you do this yourself. For example:
FROM php:8.4-fpm-alpine
RUN apk add --no-cache nginx
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
PHP & PHP-FPM.
The official PHP container correctly sets the PHP-FPM configuration error_log to /proc/self/fd/2 so you will get errors directly to stderr.
If you have display_errors = On (the default in development), errors are sent to the HTTP response. You should set display_errors = Off and ensure log_errors = On so errors go to your error_log (stderr) instead of being shown to users.
If you need to customize PHP settings, create a custom php.ini and ensure logging is configured:
config/php.ini
log_errors = On
display_errors = Off
error_log = /proc/self/fd/2
Copy it into your Dockerfile:
COPY config/php.ini /usr/local/etc/php/conf.d/custom.ini
Why does PHP use /proc/self/fd/2 and not /dev/stderr? I am not sure exactly. I recall that earlier versions of PHP may have issues with /dev/stderr. It may be something internal to PHP-FPM. If you have the answer I would love to know!
Let’s chat about this /proc path for a brief second.
/procis a pseudo-filesystem (procfs) in Linux that provides an interface to kernel data structures and process information. It’s not a real filesystem on disk. -/proc/selfis a symbolic link that always points to the/proc/[pid]directory of the currently executing process. This is convenient because you don’t need to know the process ID./proc/self/fd/is a directory containing symbolic links to all file descriptors opened by the current process./proc/self/fd/2is a symbolic link to wherever file descriptor 2 (standard error/stderr) points for that process.
Basically, that path lets us write to the stderr I/O device that Linux created when the PHP-FPM process was started. Pretty handy!