How to run cron jobs inside docker

TLDR;

When running cron as your ENTRYPOINT in a container, make sure you start cron in foreground mode (cron -f) and consider redirecting your job's stdout and stderr to crons (eg. */1 * * * * root /app1/test.sh > /proc/1/fd/1 2>/proc/1/fd/2). You can find an complete Dockerfile here.

Dockers Entrypoint

Docker is build for running one process per container. That doesn't mean that you can't run more than one process per container, it's just that docker only cares about the first process of an image. This process is what you specify using the ENTRYPOINT in your Dockerfile and it's the process that docker starts when you run an image.

Should that process terminate, docker will stop the container and if that process writes to stdout or stderr, docker will make that information available via docker logs [container] and if you run the image attached your keyboard input will be forwarded to your ENTRYPOINTs stdin.

If you want to run multiple processes, you can start additional ones from your ENTRYPOINT. Docker doesn't care about those child processes. Should one of them terminate, your container will continue to run - if they die, they die. And if they write stuff to stdout or stderr, docker doesn't know and doesn't care.

cron is a daemon

cron is usually run as a daemon so on startup cron forks, which means it creates a running copy of itself and then terminates the original process.

Which is normally what you want, but when running inside a container docker will stop your container when your main process exits.

Running Cron inside a Docker image

The way solve that problem is to instruct cron to start in foreground mode, so that it doesn't fork by using:

cron -f

By running cron in foreground mode you can use it as an ENTRYPOINT in your Dockerfile and it will execute the jobs you've specified in your crontab file.

Logging of cron jobs

There is one problem left, which is that the output of those jobs don't show up in the docker logs, because docker only tracks the stdout/stderr of the first process.

A simple solution for this problem is just redirecting the stdout/stderr of the child processes to the main process (which will always have PID 1), e.g.:

*/1 * * * * root /app1/test.sh > /proc/1/fd/1 2>/proc/1/fd/2

An complete example

Now to put it all together here is a complete Dockerfile as an example:

FROM ubuntu

# install cron
RUN apt-get update && apt-get install cron -y -qq

# create two test applications that we will launch using cron
RUN mkdir /app1 && echo 'echo `date +"%H:%M:%S"` - This is sample application 1!' > /app1/test.sh && chmod +x /app1/test.sh
RUN mkdir /app2 && echo 'echo `date +"%H:%M:%S"` - This is sample application 2!' > /app2/test.sh && chmod +x /app2/test.sh

# register cron jobs to start the applications and redirects their stdout/stderr
# to the stdout/stderr of the entry process by adding lines to /etc/crontab
RUN echo "*/1 * * * * root /app1/test.sh > /proc/1/fd/1 2>/proc/1/fd/2" >> /etc/crontab
RUN echo "*/2 * * * * root /app2/test.sh > /proc/1/fd/1 2>/proc/1/fd/2" >> /etc/crontab

# start cron in foreground (don't fork)
ENTRYPOINT [ "cron", "-f" ]

If you build and run it:

docker build . -t cron
docker run cron

And wait for a couple of minutes and you should see the something similiar following output:

17:03:01 - This is sample application 1!
17:04:01 - This is sample application 1!
17:04:01 - This is sample application 2!
17:05:01 - This is sample application 1!
17:06:01 - This is sample application 1!
17:06:01 - This is sample application 2!
17:07:01 - This is sample application 1!
17:08:01 - This is sample application 1!
17:08:01 - This is sample application 2!
17:09:01 - This is sample application 1!
17:10:01 - This is sample application 1!
17:10:01 - This is sample application 2!
17:11:01 - This is sample application 1!
17:12:01 - This is sample application 1!
17:12:01 - This is sample application 2!

References

Related Articles