Search code examples
dockercroncontainerssh

How to run a cron job inside a docker container?


I am trying to run a cronjob inside a docker container that invokes a shell script.

How can I do this?


Solution

  • You can copy your crontab into an image, in order for the container launched from said image to run the job.


    Important: as noted in docker-cron issue 3: use LF, not CRLF for your cron file.


    See "Run a cron job with Docker" from Julien Boulay in his Ekito/docker-cron:

    Let’s create a new file called "hello-cron" to describe our job.

    # must be ended with a new line "LF" (Unix) and not "CRLF" (Windows)
    * * * * * echo "Hello world" >> /var/log/cron.log 2>&1
    # An empty line is required at the end of this file for a valid cron file.
    

    If you are wondering what is 2>&1, Ayman Hourieh explains.

    The following Dockerfile describes all the steps to build your image

    FROM ubuntu:latest
    MAINTAINER [email protected]
    
    RUN apt-get update && apt-get -y install cron
    
    # Copy hello-cron file to the cron.d directory
    COPY hello-cron /etc/cron.d/hello-cron
     
    # Give execution rights on the cron job
    RUN chmod 0644 /etc/cron.d/hello-cron
    
    # Apply cron job
    RUN crontab /etc/cron.d/hello-cron
     
    # Create the log file to be able to run tail
    RUN touch /var/log/cron.log
     
    # Run the command on container startup
    CMD cron && tail -f /var/log/cron.log
    

    But: if cron dies, the container keeps running.

    (see Gaafar's comment and How do I make apt-get install less noisy?:
    apt-get -y install -qq --force-yes cron can work too)

    As noted by Nathan Lloyd in the comments:

    Quick note about a gotcha:
    If you're adding a script file and telling cron to run it, remember to
    RUN chmod 0744 /the_script
    Cron fails silently if you forget.

    Warning: as noted in the comments by user8046323

    This config schedules tasks two times.

    • One time with crontab and
    • one time with cron.d

    Please use only one of the ways to evade scheduling your tasks twice

    True: the problem is with those two lines in the above Dockerfile:

    COPY hello-cron /etc/cron.d/hello-cron
    RUN crontab /etc/cron.d/hello-cron
    
    • By placing the hello-cron file in the /etc/cron.d directory, you automatically schedule the cron jobs contained in this file. The cron daemon checks this directory for any files containing cron schedules and automatically loads them.

    • The crontab command with /etc/cron.d/hello-cron takes the contents of the hello-cron file and loads them into the main crontab. This means the same jobs are now scheduled directly in the crontab as well, effectively duplicating them.

    you should choose one method to manage your cron jobs, depending on your specific needs:

    • If you prefer using /etc/cron.d (often easier for managing multiple separate cron job files):

      COPY hello-cron /etc/cron.d/hello-cron
      RUN chmod 0644 /etc/cron.d/hello-cron
      
    • If you prefer using crontab (gives you a consolidated view of all cron jobs and can be easier for a single or a few jobs):

      ADD hello-cron /etc/cronjob
      RUN crontab /etc/cronjob
      

    OR, make sure your job itself redirect directly to stdout/stderr instead of a log file, as described in hugoShaka's answer:

     * * * * * root echo hello > /proc/1/fd/1 2>/proc/1/fd/2
    

    Replace the last Dockerfile line with

    CMD ["cron", "-f"]
    

    But: it doesn't work if you want to run tasks as a non-root.

    See also (about cron -f, which is to say cron "foreground") "docker ubuntu cron -f is not working"


    Build and run it:

    sudo docker build --rm -t ekito/cron-example .
    sudo docker run -t -i ekito/cron-example
    

    Be patient, wait for 2 minutes and your command-line should display:

    Hello world
    Hello world
    

    Eric adds in the comments:

    Do note that tail may not display the correct file if it is created during image build.
    If that is the case, you need to create or touch the file during container runtime in order for tail to pick up the correct file.

    See "Output of tail -f at the end of a docker CMD is not showing".


    See more in "Running Cron in Docker" (Apr. 2021) from Jason Kulatunga, as he commented below

    See Jason's image AnalogJ/docker-cron based on:

    • Dockerfile installing cronie/crond, depending on distribution.

    • an entrypoint initializing /etc/environment and then calling

      cron -f -l 2