Using supervisord as cron inside Docker containers

Recently, while rebuilding my local Git repository server to run within Docker
(docker-source-vault [1]), I was once again faced with the problem of having to
run multiple programs at the same time within the same container project, a task
that's usually solved by either breaking things into multiple containers and
orchestrating everything with 'docker compose', by using a simple entrypoint
shell script [2] and sending all programs to the background, or the correct way,
which is to use a process manager, as is adviced by Docker themselves [3].

Since my use case required that services such as SSH and Apache were properly
monitored and cared for, as no one wants their repositories offline, I went down
the process manager route with supervisord [4]. This solution has proved very
reliable in production so it was a safe bet, although this time, for the first
time, I had to have something akin to cron inside my container, since I have a
repository synchronization script that should run every 5 minutes, and ensures
that I mirror all my local changes to GitHub.

Having cron inside a container is a really bad idea, since it's not really
designed to work in that specific environment, not only that, but since we
already have supervisord, we should have everything that's needed to run a task
at a fixed interval, since it has an event system [5] that supports TICK events.

The problem with supervisord's TICK events is that they only work in fixed
intervals of every 5 seconds, every minute, or every hour, meaning they are not
arbitrary, which is a big problem, and of course people have asked for
solutions [6] and the developers simply tell them to:

Subscribe to one TICK_* event and increment a counter each time the event is
received. When a certain number of counts have occurred, act and reset the
count.

This is great, except there's no documentation on how to properly do this from a
simple shell script and adhering to the event system's communication protocol.

After digging through the event system documentation and understanding how to
properly manage event states [7], I came up with the following solution using
bash:

#!/bin/bash

# Go into ready state and wait for the event to come in.
echo 'READY'
read -s eventinfo
echo "$eventinfo" 1>&2

# Get the counter value.
counter=0
counterfile=/tmp/counter  # TODO: Change this if you have multiple events.
if [ -f "$counterfile" ]; then
    counter=$(head -n 1 "$counterfile")
fi

# Increment the counter and store it.
counter=$((counter+1))
echo "$counter" > "$counterfile"
echo "Counter: $counter" 1>&2

# Check if we've reached our timer goal.
if [ "$counter" -lt "$TICK_GOAL" ]; then
    # See you next time.
    echo -e -n "RESULT 2\nOK"
    exit 0
else
    # Reset the counter.
    counter=0
    echo "$counter" > "$counterfile"
fi

# TODO: Put here everything that you need to run at a fixed interval.

# Notify we processed the event.
echo -e -n "RESULT 2\nOK"

This shell script performs all the necessary communications with supervisord in
order to keep it happy and can have its time interval configured by selecting
the right combination of event type and an environment variable called
TICK_GOAL, allowing for completely arbitrary intervals.

Here's an excerpt of the supervisord.conf used to perform the synchronization
task every 5 minutes:

[eventlistener:gitsync]
command = /git-scripts/auto-sync
events = TICK_60
environment = TICK_GOAL="5"
autostart = true
autorestart = true
redirect_stderr = false
stderr_logfile = /logs/event_%(program_name)s.log
stderr_logfile_maxbytes = 2MB
stderr_logfile_backups = 10

That's all you need to have tasks running at fixed intervals inside your
containers.


[1]: https://github.com/nathanpc/docker-source-vault
[2]: https://docs.docker.com/engine/containers/multi-service_container/#use-bash-job-controls
[3]: https://docs.docker.com/engine/containers/multi-service_container/#use-a-process-manager
[4]: https://supervisord.org/
[5]: https://supervisord.org/events.html
[6]: https://narkive.com/hPOo79mM
[7]: https://supervisord.org/events.html#event-listener-states