Docker scheduling made simple
I have a utility which I want to run within Docker periodically. The Docker container’s sole purpose is to run this utility, providing a controlled environment along with the necessary dependencies. The utility can be a shell script, python, a pre-compiled binary… anything.
The natural solution is to schedule a periodic docker run execution through crontab. I don’t like it for a few reasons:
- Automating and making it repeatable is challenging; changes and migrations require extra caution.
- It’s difficult to tell if it’s working as intended. Crontab time expressions are difficult to read, errors are easy. Observability is non-existent.
- The desired functionality is fragmented across two locations.
Another obvious approach is to write a script like this,
#!/bin/sh
while true; do
/my_script.sh
sleep 900
done
but maybe we can avoid it.
I came up with a different idea. We can just use this entrypoint for the container:
watch -n 900 flock -n /tmp/my_script -c /my_script.sh
Let’s dissect what this entrypoint does:
watch: a command-line utility that keeps executing a command at regular intervals. Included everywhere by default, including Alpine Linux.-n 900: Frequency in seconds. In this example I’m running it every 15 minutes (900 seconds).flock -n /tmp/my_script:flockis another ubiquitous OS utility. In the rare case where the previous execution hasn’t finished, it will skip the new one.-nmeans not blocking: if it can’t acquire the exclusive lock, it will stop trying immediately until the next interval./tmp/my_scriptis what it will use as a lock, just designate a unique location. This step is optional, but I use it defensively.-c /my_script.sh:-cfollowed by the command you actually want to run.- No helper script needed.
If overlapping executions are not a concern, you can simply use watch -n 900 /my_script.sh.
Compared with the cons I had lined out:
- It’s self-contained. It’s all within the Dockerfile and will be tracked in git.
- Easy observability, at any given moment you can run
docker psto see your container is running. - The interval is specified with the number of seconds, which is much easier to interpret than a crontab expression.
My Dockerfiles look something like this:
FROM alpine:3.20
# 'watch' and 'flock' are already included in the base image, no need to install them
# build my_program
ENTRYPOINT ["watch", "-n", "900", "flock", "-n", "/tmp/my_program", "-c", "./my_program"]
Tip: I run a few like these and I name all these containers keep_{action}ing. It makes it easy to understand what they do and it’s a way to distinguish them at first glance from other fundamentally different containers I run.