Recently I've had an interesting conversation in #docker on Freenode with a guy that's been trying to get crons working inside his Docker container. I hadn't yet had a chance to look at that, and so we took off on a late-night debug session exchanging Dockerfiles via Pastebin. He has a bunch of other stuff going on, but at the core, he's just running an Apache webserver instance and then wants to run some crons in that container as well. I took his Dockerfile and related scripts, and pared them down to the bare minimum, commenting out everything that wasn't related directly to getting Apache and cron to work. You can take a look at what I came up with:

FROM ubuntu:14.04
MAINTAINER curtisz <software@curtisz.com>

# we install stuff this way to keep it all on one layer
# (which reduces the overall size of our image)
RUN apt-get update -y && \
	apt-get install -y \
		cron \
		apache2 \
		supervisor && \
	rm -rf /var/lib/apt/lists/*

# apache stuff
RUN mkdir -p /var/lock/apache2 /var/run/apache2 /etc/supervisor/conf.d/
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
ENV APACHE_PID_FILE /var/run/apache2.pid
# empty out the default index file
RUN echo "" /var/www/html/index.html

# cron job which will run hourly
# (remember that COPY is better than ADD for plain files or directories)
COPY ./crons /etc/cron.hourly/
RUN chmod +x /etc/cron.hourly/crons
# test crons added via crontab
RUN echo "*/1 * * * * uptime >> /var/www/html/index.html" | crontab -
RUN (crontab -l ; echo "*/2 * * * * free >> /var/www/html/index.html") 2>&1 | crontab -

# supervisord config file
COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf
 
EXPOSE 80
WORKDIR /var/www/html/
CMD /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

You can tell what's going on here, it's pretty straightforward. To get this working, we need to install apache2, supervisor, and of course, cron. The next few lines are configuration options for Apache. Then finally we get to the test crons. I'm dumping a simple cron shell script into /etc/cron.hourly in the container and making it executable, and then creating two new crons via crontab. Please note how I have added these crons with crontab - by also listing the previously-added crons with crontab -l. If you don't do this, whatever you add via crontab will overwrite whatever's in there now. My crons are just stupid simple crons for dumping something easy into Apache's index.html file so we can prove they're running. My little crons file looks like this:

#!/bin/sh
ps aux | grep apache > /var/www/html/index.html

Some things to notice about this file... First, it starts with a typical shell script shebang (#!/bin/sh). It should also be executable. Lastly, this file can only contain alphanumeric characters in its filename (and underscores). It cannot contain a dot, which means naming it something like crons.sh won't work.

Next, we're adding the config file for supervisord. Then simply exposing the port, changing our working directory, and then specifying our CMD to start supervisord. Speaking of, this is what our supervisord.conf file looks like:

[supervisord]
nodaemon=true
logfile = /var/log/supervisord.log
logfile_maxbytes = 50MB
logfile_backups=10
 
[program:cron]
autorestart=false
command=cron -f
 
[program:apache2]
autorestart=false
command=/usr/sbin/apache2ctl -D FOREGROUND

Pretty standard fare. And so let's build our container and start it:

docker build -t="crontest" .
docker run -it --name="crontest" -p 8080:80 crontest:latest

And there you have it! Give your crons a few minutes to execute, then fire up a browser on your localhost and point it to http://localhost:8080/index.html. You should see the output of our test crons there at the tail of the file. Refresh to see more.

That's all there is to it! Previously, I've used the cron system available on the Docker host, which certainly has its benefits. First and foremost of which is not having to use supervisord inside a Docker container. Since Docker is just a fancy way to run a process, you want to avoid loading up your container with a bunch of cruft. It's not a VM! But when you can't avoid it and you really need to run crons alongside your containerized processes, it's no sweat to get it going.