How to Run Cron Jobs with the Whenever Gem in a Docker Container

Whew! That's a long title, isn't it? I'm going to show you how to get cron jobs working in a Docker container. Specifically, I'll be using the whenever gem to create those cron jobs.

Copy environment variables to crontab

First, we're going to add a couple lines to our schedule.rb file so that whenever will copy our environment variables into our crontab. We do this because cron jobs are run with a mostly empty environment. If we don't do this, our jobs will have a hard time finding the binaries they need:

# schedule.rb
...
ENV.each_key do |key|
  env key.to_sym, ENV[key]
end
...

These lines iterate over all of the environment variables that are set at the time that bundle exec whenever --update-crontab is run and copies them into the crontab file that is generated. This way we have access to the same exact environment variables in our jobs as we do in our normal interactive shell.

Set Rails environment

Next, we need to add one more line to our schedule.rb file.

# schedule.rb
...
set :environment, ENV["RAILS_ENV"]
...

This line is needed to make sure that our RAILSENV variable is respected by our cron jobs. The whenever gem explicity sets the RAILS_ENV variable in the crontab job lines that it creates and overrides whatever it was set to. The line above ensures that the jobs in the crontab use the RAILSENV setting that was present when the crontab file was created.

Run Docker container

Finally, we need to run the Docker container. Here's an example Dockerfile to get you started:

# Dockerfile

FROM ruby:2.3

RUN apt-get update && \
  apt-get install -qq -y --no-install-recommends cron && \
  rm -rf /var/lib/apt/lists/*

ENV APP_HOME /usr/src/app
ENV RAILS_LOG_TO_STDOUT true
ENV RAILS_ENV production

RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

COPY Gemfile $APP_HOME/Gemfile
COPY Gemfile.lock $APP_HOME/Gemfile.lock
RUN bundle install

COPY . $APP_HOME

CMD bash -c "bundle exec whenever --update-crontab && cron -f"

The important parts to note are explained below.

This line installs cron:

...
RUN apt-get update && \
  apt-get install -qq -y --no-install-recommends cron && \
  rm -rf /var/lib/apt/lists/*
...

And this line updates the crontab and runs cron:

...
CMD bash -c "bundle exec whenever --update-crontab && cron -f"

And that's it! The biggest challenge in getting this to run was figuring out that environment variables can be copied into the crontab. Before that I ran into so many issues with jobs not being able to find the programs they needed.

Photo by Glenn Carstens-Peters