KeiruaProd

Some cron tips

I had a surprisingly hard time setting up some cron tasks inside of a docker container that does other things. Here are some things to note, which are also useful linux things to know.

cron vs systemd timer

I picked cron out of habit, because I thought all I had to do was add something like * * * * * myscript.sh in the right location. Maybe some of the following problems could have been avoided (or replaced by others ;)) with a systemd timer.

logging

Cron file locations

Things are all over the place:

What should you do, what are the differences, what syntax should you use, who should own those files ? On Debian, you should read cron(8), which is well summarized here:

The main difference is that /etc/cron.d is populated with separate files, whereas crontab manages one file per user; it’s thus easier to manage the contents of /etc/cron.d using scripts (for automated installation and updates), and easier to manage crontab using an editor (for end users really). Other important differences are that not all distributions support /etc/cron.d, and that the files in /etc/cron.d have to meet a certain number of requirements (beyond being valid cron jobs): they must be owned by root, and must conform to run-parts’ naming conventions (no dots, only letters, digits, underscores, and hyphens). If you’re considering using /etc/cron.d, it’s usually worth considering one of /etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly, or /etc/cron.monthly instead.

cron does not load the environment

…not even PATH, so your command may crash because the binary is not found without the absolute path.

You have to provide the environment you need somehow. Some solutions:

Running cron on top of another program inside docker

Docker is meant to run only one program, so in theory you should not run both a program and cron. A solution is to use a Docker entrypoint at the end of your Dockerfile:

COPY your-cron-task /etc/cron.d/your-cron-task
## a custom entry point − needed by cron
COPY config/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

CMD the_command_you_actually_want_to_run

and entrypoint.sh:

#!/bin/bash

# We want the environment to be available to CRON
# https://stackoverflow.com/questions/27771781/how-can-i-access-docker-set-environment-variables-from-a-cron-job
printenv | grep -v "no_proxy" >> /etc/environment

# When we run your app, we also want to start the cron service
crontab /etc/cron.d/your-cron-task
service cron start

# Hand off to the CMD
exec "$@"

Add a lock in order to prevent double execution

You may not want to start off a new copy of your script before the old one has finished. A lock seems to be a solution:

#!/bin/bash

LOCKFILE="/var/lock/`basename $0`"

(
flock -n 9 || {
echo$0 already running”
exit 1
}

# The command you actually want to run

) 9>$LOCKFILE

Cron rules are hard to read

Not critical and not that hard, but:

See a typo ? You can suggest a modification on Github.