Forwarding Nginx Logs to Docker

Callback Insanity
5 min readJan 14, 2020

--

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:

Woops! Something wrong with that horse

Upon closer inspection of the error, I can verify the reason why Nginx is failing to start up:

FAILURE: No access to /dev/stdout

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)
Something wrong?

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 value stderr 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:

Using special value stderr for error_log; access_log just pointing to /dev/stdout

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:

When you leave out tty: true in docker compose

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:

No Errors To See Here, Move On

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:

--

--

Callback Insanity

Organic, fair-sourced DevOps and Full-Stack things. This is a BYOB Establishment — Bring Your Own hipster Beard.