Skip to content

Instantly share code, notes, and snippets.

@mowings
Last active May 8, 2024 11:54
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mowings/59790ae930accef486bfb9a417e9d446 to your computer and use it in GitHub Desktop.
Save mowings/59790ae930accef486bfb9a417e9d446 to your computer and use it in GitHub Desktop.
Run cron on docker with job output directed to stdout

You'd like a docker container that runs cron jobs, with the output of those cron jobs going to stdout so they are accessible as docker logs. How do you do that?

Install cron, and set it up to run in the foreground

In your Dockerfile, apt-get -y install cron Use apk or whatever if you are running on alpine or another distribution Set the command or entrypoint in your Dockerfile to run cron 9n the foreground

ENTRYPOINT ["/usr/sbin/cron", "-f"]

Create the crontab with redirection

Copy the crontab with your jobs in it into /etc/crontab. Each job should redirect it's stdout and stderr to fh 1 of PID 1 (the crontab process). Here's a simple example crontab:

SHELL=/bin/sh 
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

* * * * * root echo "HELLO!" > /proc/1/fd/1 2>&1

This just prints "HELLO" every minute, redirecting the echo output to process 1's stdout (file handle 1). This line also redirects stderr, although that is not really necessary in the case of a simple echo.

This works because docker always treats the stdout from process 1 as the docker log stream.

An even simpler way

... to run a single job on a regular basis is to use date and sleep. This makes for a simpler container (no need for cron) if you only need a command or commands to run at a single interval (say, every morning at 3:00AM). Details in this gist

@x-yuri
Copy link

x-yuri commented Nov 6, 2020

In other words (with logging):

docker-compose.yml:

version: '3'
services:
  cron:
    build: .
    command: sh -euc '
      /usr/sbin/rsyslogd -n &
      /usr/sbin/cron -fL 15
      '
FROM php:7.4-fpm
RUN apt-get update \
    && apt-get install -y cron rsyslog \
    # make pam_env not complain about missing /etc/default/locale
    && touch /etc/default/locale \
    # don't let rsyslogd complain about imklog
    && sed -i '/imklog/s/^/#/' /etc/rsyslog.conf
COPY site /etc/cron.d/site
COPY rsyslog.conf /etc/rsyslog.d/console.conf

rsyslog.conf:

*.* /dev/stdout

site:

* * * * * root echo job1: $(date +\%Y\%m\%d-\%H\%M\%S) >/proc/1/fd/1 2>&1
* * * * * root { echo job2: $(date +\%Y\%m\%d-\%H\%M\%S); exit 1; } >/proc/1/fd/1 2>&1

Output:

cron_1  | Nov  6 22:38:09 de6267fffb08 rsyslogd:  [origin software="rsyslogd" swVersion="8.1901.0" x-pid="6" x-info="https://www.rsyslog.com"] start
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[10]: pam_unix(cron:session): session opened for user root by (uid=0)
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[11]: pam_unix(cron:session): session opened for user root by (uid=0)
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[11]: (root) CMD ([12] echo job1: $(date +%Y%m%d-%H%M%S) >/proc/1/fd/1 2>&1)
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[10]: (root) CMD ([13] { echo job2: $(date +%Y%m%d-%H%M%S); exit 1; } >/proc/1/fd/1 2>&1)
cron_1  | job2: 20201106-223901
cron_1  | job1: 20201106-223901
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[10]: (CRON) error (grandchild #13 failed with exit status 1)
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[10]: (root) END ([13] { echo job2: $(date +%Y%m%d-%H%M%S); exit 1; } >/proc/1/fd/1 2>&1)
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[10]: pam_unix(cron:session): session closed for user root
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[11]: (root) END ([12] echo job1: $(date +%Y%m%d-%H%M%S) >/proc/1/fd/1 2>&1)
cron_1  | Nov  6 22:39:01 de6267fffb08 CRON[11]: pam_unix(cron:session): session closed for user root

Or the alpine version.

@x-yuri
Copy link

x-yuri commented Jun 28, 2022

I probably made an example with rsyslog because in some cases crontab can be generated (e.g. ruby's whenever), I'm not sure. The suggested solution works:

docker-compose.yml:

services:
  d:
    build: .
    init: yes

Dockerfile:

FROM debian:bullseye-slim
RUN set -x && apt update && apt install -y cron \
    && echo '* * * * * root echo "HELLO!" > /proc/1/fd/1 2>&1' > /etc/cron.d/myjob
CMD ["/usr/sbin/cron", "-f"]

Under Alpine Linux:

docker-compose.yml:

services:
  d:
    build: .

Dockerfile:

FROM alpine
RUN set -x && apk add --no-cache dcron \
    && echo '* * * * * echo "HELLO!" > /proc/1/fd/1 2>&1' > /etc/cron.d/myjob
CMD ["/usr/sbin/crond", "-f"]

Which exits with:

setpgid: Operation not permitted

But it can be worked around like so:

docker-compose.yml:

services:
  d:
    build: .
    init: yes

Dockerfile:

FROM alpine
RUN set -x && apk add --no-cache dcron \
    && echo '* * * * * echo "HELLO!" > /proc/1/fd/1 2>&1' > /etc/cron.d/myjob
COPY dcron.sh .
CMD ["/dcron.sh"]

dcron.sh:

#!/bin/sh -e
# https://github.com/dubiousjim/dcron/issues/13#issuecomment-340382335
crond -f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment