Forwarding Nginx Logs to Docker
Preface
It is fairly common practice in Docker to forward the logs from a container’s main process (as defined by CMD
or ENTRYPOINT
instructions) into stdout
and stderr
. The reasoning behind this decision is that the Docker daemon automatically collects any output sent to stdout
and stderr
, allowing you to tail the standard output and errors of your container using for example docker-compose logs -f [service name]
.
Without forwarding, say — the logs from Nginx to standard error and out, you would not know what Nginx is up to. Like what requests is serving, or what errors is encountering along the way.
The Situation
Alpine Linux version 3.x by default has a group called tty
. But only root
has access to it.
By default it also has a user called nobody
that I like to use for privilege deescalation (stepping down from root
), however the nobody
user is not part of the tty
group and therefore does not have it’s privileges.
For context, the tty
group grants permissions to write to Linux system devices under /dev
, such as /dev/stdout
and /dev/stderr
.
Since the pre-existing user nobody
that ships with Alpine Linux has no access to the tty
group, if you run Nginx as a non-root user using the following command:
# Dockerfile for Nginx
CMD [ "exec su-exec nobody /usr/sbin/nginx -g 'daemon off;'" ]
Nginx will even refuse to start because out of the box, the user nobody
can’t write to /dev/stdout
or /dev/stderr
.
In this example, I attempt to start Nginx as user nobody
using su-exec
, and I get an Exit 1
response from Nginx:
Upon closer inspection of the error, I can verify the reason why Nginx is failing to start up:
The error coming from the Nginx container reads:
nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (13: Permission denied)2020/01/14 01:03:23 [emerg] 1#1: open() "/dev/stdout" failed (13: Permission denied)
3 Quick Steps To Forward Logs From Nginx to Docker As Non-Root User
Step 1. Give the non-root user access to tty group
Give the user nobody
access to the tty
group so it can write to /dev/stdout
and /dev/stderr
.
In my Dockerfile for Nginx, I have:
addgroup nobody tty
To verify that the Nginx container nobody
user has access to the tty
group, use the getent group
command:
docker-compose up -d
docker-compose exec nginx sh
/ # getent group | grep tty
tty:x:5:nobody
Step 2. Ensure Nginx logs point to stdout and stderr
In order for Docker logs to capture output from Nginx, you have to tell Nginx to write to /dev/stdout
and /dev/stderr
.
In my nginx.conf
I have:
# nginx.conf
error_log stderr warn;
access_log /dev/stdout main;
According to the official docs, Nginx does support logging to stderr
out-of-the-box:
The first parameter defines a
file
that will store the log. The special valuestderr
selects the standard error file
Credits go to these two Stack Overflow answers for pointing me in the right direction.
Instead of just using the stderr
“special” value, you can also just point the error_log
and access_log
directives to the /dev/std*
files themselves.
In my case I’m using the stderr
special value for error_log
since it’s supported, then pointing access_log
directly to /dev/stdout
.
Here’s the proof, because math and science:
Step 3. Ensure Docker allocates tty
The final step is to ensure that /proc/self/fd/0,1,2
file descriptors are accessible by the Docker container, regardless if using vanilla Docker or Docker Compose.
For example, /dev/stdout
points to file descriptor/proc/self/fd/1
, which in turn points to device /dev/pts/0
or similar.
If you don’t tell Docker to allocate the pts
device using the tty
flag in Docker Compose or -t
in Docker, then writing to /dev/stdout
when running the container as non-root fails. For example, if I comment out tty: true
in my Nginx’s Docker Compose definition, this is what I get:
Running the container as root does not have these issue because the pts
devices are always initialized regardless if you’re running with tty
turned on or not, so log forwarding just works if the user running the Nginx process in the Docker container is root
.
As a non-root user however, you have to make use of the tty
flag to make sure that pts
exists, and therefore writable to by the Nginx non-root user.
This Github issue for Docker contains the details about pts
allocation.
This is how I allocate the pts
devices using Docker Compose for Nginx, note line #3:
After following those quick three steps I can verify that Nginx successfully starts as a non-root user with no errors:
Conclusion
That concludes one of the fastest Medium.com posts that I have written so far.
I strove to write this in 30 minutes but the post was probably gone in 60 minutes.
These were the 3 things I felt I needed to document in order to get log forwarding to work properly: Nginx log configuration, Docker tty
options, and granting Linux non-root users access to the tty
group.
As always, clap the article if you found it was useful — I might get paid 25 cents a month!
Bibliography:
- https://www.docker.com/blog/tips-for-deploying-nginx-official-image-with-docker/
- https://github.com/moby/moby/issues/31243#issuecomment-402239403
- https://stackoverflow.com/questions/22541333/have-nginx-access-log-and-error-log-log-to-stdout-and-stderr-of-master-process/23328458#23328458
- https://stackoverflow.com/questions/22541333/have-nginx-access-log-and-error-log-log-to-stdout-and-stderr-of-master-process/29951559#29951559