Search code examples
phpdockerstdoutnamed-pipes

Tailing named pipe in Docker to write to stdout


My Dockerfile:

FROM php:7.0-fpm

# Install dependencies, etc

RUN \
    && mkfifo /tmp/stdout \
    && chmod 777 /tmp/stdout

ADD docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

as you can see I'm creating a named pipe at /tmp/stdout. And my docker-entrypoint.sh:

#!/usr/bin/env bash

# Some run-time configuration stuff...

exec "./my-app" "$@" | tail -f /tmp/stdout

My PHP application (an executable named my-app) writes its application logs to /tmp/stdout. I want those logs to then be captured by Docker so that I can do docker logs <container_id> and see the logs that the application wrote to /tmp/stdout. I am attempting to do this by running the my-app command and then tailing /tmp/stdout, which will then output the logs to stdout.

What I'm seeing happen is that when I run my application, it hangs when it writes the first log message. I believe this happens because there is nothing "reading" from the named pipe, and writing to a named pipe blocks until something reads from it. This is confirmed if I do docker exec -it <container_id> bash, and then do tail -f /tmp/stdout myself inside the container. Once I do that, the container immediately exits because the application has written its logs to the named pipe.

For reasons that I won't bloat this post with, it's not possible for my-app itself to write logs to stdout. It has to use /tmp/stdout.

Can anybody tell me why this isn't working, and what I need to change? I expect I have to change the exec call in docker-entrypoint.sh, but I'm not sure how. Thank you!


Solution

  • What I'm seeing happen is that when I run my application, it hangs when it writes the first log message. I believe this happens because there is nothing "reading" from the named pipe, and writing to a named pipe blocks until something reads from it.

    This is correct, see fifo(7). But with your example code

    exec "./my-app" "$@" | tail -f /tmp/stdout
    

    this should actually work since the pipe will start ./my-app and tail simultaneously so that there is something reading from /tmp/stdout.

    But one problem here is that tail -f will never terminate by itself and so neither your docker-entrypoint.sh/container. You could fix this with:

    tail --pid=$$ -f /tmp/stdout &
    exec "./my-app" "$@"
    

    tail --pid will terminate as soon as the process provided by id terminates where $$ is the pid of the bash process (and through exec later the pid of ./my-app).


    For reasons that I won't bloat this post with, it's not possible for my-app itself to write logs to stdout. It has to use /tmp/stdout.

    Does this mean it has to write to a filesystem path or is the path /tmp/stdout hardcoded?

    If you can use any path you can use /dev/stdout / /proc/self/fd/1 / /proc/$$/fd/1 as logging path to let your application write to stdout.

    If /tmp/stdout is hardcoded try symlinking it to /dev/stdout:

    ln -s /dev/stdout /tmp/stdout