27

I am running "cron" in a docker container.
Every day a script is executed.
The output of this script I would like to see via "docker logs "

The process with PID 0 is the cron daemon in my container. Entrypoint starts cron in foreground:

/usr/sbin/crond -f 

I understand, that I could redirect the script output to a file "path/to/logs"

07 2 * * * /data/docker/backup_webserver/backupscript.sh >> path/to/logs

and start the container as following to see the logs

"tail -f path/to/logs" 

But then the file "path/to/logs" would grow during the runtime of the container.
Is there a possibility to log from crontab, directly to "docker logs" ?

Skip
  • 6,240
  • 11
  • 67
  • 117
  • I've created a repo to work this out: https://github.com/tomsaleeba/alpine-cron-test. The answer seems to be to not add any extra redirects and it'll "just work", at least for my simple `echo` tests. – Tom Saleeba Mar 19 '19 at 06:21
  • [This answer](https://stackoverflow.com/questions/37458287/how-to-run-a-cron-job-inside-a-docker-container/46220104#46220104) was the only effective approach in my situation. – Stano Mar 28 '19 at 13:34

6 Answers6

28

Change your cron file to below

07 2 * * * /data/docker/backup_webserver/backupscript.sh > /dev/stdout

This will make sure the logs go to the container output

Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • 1
    This works on Alpine when started with `crond -l 2 -f` – ProGirlXOXO Aug 25 '18 at 00:04
  • 5
    Why does this work? I thought `/dev/stdout` would actually go to `/proc/self/fd/1` and for the process that cron starts, this won't be the `/proc/1/fd/1` file that docker monitors. – Tom Saleeba Dec 20 '18 at 03:15
  • I don't think this will work because /dev/stdout is a link to STDOUT of the process accessing it. So by doing foo > /dev/stdout, you're saying "redirect my STDOUT to my STDOUT". Kinda doesn't do anything :-). – LeDerp Mar 07 '19 at 16:51
  • To add to my own comment, I think cron is gathering the output from the job and passing it to *its* stdout, which *is* the one that docker monitors. – Tom Saleeba Mar 19 '19 at 06:19
  • @TomSaleeba, yes because the redirection is run the cron's process and that is why stdout is that of the cron only – Tarun Lalwani Mar 20 '19 at 09:05
  • `> /dev/stdout` is not a good option for production. I recommend to check mkfifo – Paul Serikov Jun 28 '19 at 16:42
  • this doesn't work for me. ```docker logs``` still returns nothing – trevalexandro Nov 17 '20 at 20:55
  • 1
    Looks like it only works if crond is running in foreground. – iMx Jun 28 '21 at 11:00
  • if we use this answer's cron in side docker container should we also use `CMD cron -f && tail -f /var/log/cron.log` inside dockerfile ? – Chang Zhao Oct 05 '21 at 19:50
  • Thanks @TomSaleeba!! :) For me, this works: `* * * * * something > /proc/1/fd/1` – ivop Feb 06 '23 at 13:49
  • 1
    Or even better: `* * * * * something > /proc/1/fd/1 2>&1` – ivop Feb 06 '23 at 13:58
22

Alpine: No need for redirection

using the default cron utility (busybox)

Dockerfile

FROM alpine:3.7

# Setting up crontab
COPY crontab /tmp/crontab
RUN cat /tmp/crontab > /etc/crontabs/root


CMD ["crond", "-f", "-l", "2"]

crontab

* * * * * echo "Crontab is working - watchdog 1"

Centos:

Redirection to /proc/1/fd/1 inside the crontab declaration line

Dockerfile

FROM centos:7

RUN yum -y install crontabs

ADD crontab /etc/cron.d/crontab
RUN chmod 0644 /etc/cron.d/crontab
RUN crontab /etc/cron.d/crontab


CMD ["crond", "-n"]

crontab

* * * * * echo "Crontab is working - watchdog 1" > /proc/1/fd/1

enter image description here

MaxBlax360
  • 1,070
  • 1
  • 12
  • 15
  • 5
    I'm curious... Why copy the crontab to /tmp then into /etc/crontabs/root? – Paul Dugas Jun 30 '20 at 14:28
  • 1
    Debian applies a patch to nullify the standard output, in a similar way as CentOS: https://salsa.debian.org/debian/cron/-/blob/master/debian/patches/fixes/Redirect-daemon-standard-streams-to-dev-null.patch I guess Alpine is the only one that does not apply this patch – BeardOverflow Jun 15 '21 at 14:48
  • 2
    And I suggest you to add ```2>/proc/1/fd/2``` to your answer for CentOS in order to catch errors sent to stderr – BeardOverflow Jun 15 '21 at 14:55
  • Could you please tell what does this mean: `using the default cron utility (busybox)` My ubuntu has a single busybox binary `/usr/bin/busybox`, which contains a set of applet. I need to use `busybox appletname` to access them. How can you refer to your `crond` inside a busybox without mentioning `busybox` in your command. – doraemon Jan 13 '22 at 02:12
  • After testing alot of method finally your solution worked for me. – Lily Sep 06 '22 at 08:38
  • I had some problems switching from ubuntu->alpine b/c busybox has a stripped down /usr/bin/env. Suddenly a bunch of #! lines stopped working when they have -S flags. Fix: newer alpine:3.17.1, install coreutils and bash (skip ash). – Scott Smith Jan 23 '23 at 17:17
9

@mcfedr is correct, but it took me a while to understand it with it being a one-liner with variables and some extra code related to setting up cron.

This may be a little bit easier to read. It helped me to write it out explicitly.

# Create custom stdout and stderr named pipes
mkfifo /tmp/stdout /tmp/stderr
chmod 0666 /tmp/stdout /tmp/stderr

# Have the main Docker process tail the files to produce stdout and stderr 
# for the main process that Docker will actually show in docker logs.
tail -f /tmp/stdout &
tail -f /tmp/stderr >&2 &

# Run cron
cron -f

Then, write to those pipes in your cron:

* * * * * /run.sh > /tmp/stdout 2> /tmp/stderr
kirkmadera
  • 565
  • 6
  • 13
2

fifo is the way to go, it also useful because it allows cron tasks that are not running as root to write to the output.

I am using a CMD along these lines

ENV LOG_STREAM="/tmp/stdout"
CMD ["bash", "-o", "pipefail", "-c", "mkfifo $$LOG_STREAM && chmod 777 $$LOG_STREAM && echo -e \"$$(env | sed 's/=\\(.*\\)/=\"\\1\"/')\n$$(cat /etc/cron.d/tasks)\" > /etc/cron.d/tasks && cron -f | tail -f $$LOG_STREAM"]

With the tasks in /etc/cron.d/tasks

* * * * */10 www-data echo hello >$LOG_STREAM 2>$LOG_STREAM

I also prepend the env at launch to tasks so it's visible to the tasks, as cron doesnt pass it though by itself. The sed is needed because crontab format requires env vars to be quoted - at least it requires empty vars to be quoted and fails to run tasks if you have an empty var without quotes.

mcfedr
  • 7,845
  • 3
  • 31
  • 27
1

You could just use a FIFO.

mkfifo path/to/logs

When processes exchanging data via the FIFO, the kernel passes all data without writing it to the filesystem. Thus, the FIFO special has no contents on the filesystem; the filesystem entry merely serves a reference point so that processes can access the pipe using a in the filesystem.

man fifo

1

For Debian-based images, following Dockerfile works for me (note that /etc/crontab has a slightly different format, compared to user crontab files):

FROM debian:buster-slim

RUN apt-get update \
 && apt-get install -y cron

RUN echo "* * * * * root echo 'Crontab is working - watchdog 1' > /proc/1/fd/1 2>/proc/1/fd/2" > /etc/crontab

CMD ["cron", "-f"]
mstrap
  • 16,808
  • 10
  • 56
  • 86