Alan Morales

January 21, 2025

Cronjobs using Kamal and whenever gem

I am working on a new project and decided to use Rails 8 and deploying it via Kamal to see how it fares compared to Kubernetes. I love the convenience of containers and I am very pleased to see that we have sensible defaults for developing and deploying from day one.

The default setup of Rails 8 is very pleasant to work with, and even deploying your app with Kamal once you get the initial hang of it, however I stumbled into many issues when following their documentation to use Cron.

As far as the documentation states it looks as easy as:
  • Writing your crontab to config/crontab
  • Using a custom command to run cron on your Container

This was certainly not the case. The default Rails Dockerfile uses the non root rails user, and for reasons which I still can't understand using the Kamal documentation example successfully writes the crontab of the rails user, however it never runs any of the commands you specify. After reading through some Kamal issues and PRs I was able to come up with a solution that works well enough and even integrate the whenever gem into the process.

Here's my step by step description. I am assuming you are already familiarized with the Rails project structure, whenever, Docker and Kamal:


Make sure you install cron in your Dockerfile

I installed mine right above copying the bundle gems and the application code like so:

# Final stage for app image
FROM base

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

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails


Create a cron_executor script

You will use this to execute anything you want in cron and I borrowed the idea from Glauco Custodio's blog. I saved it under bin/cron_executor:

#!/bin/bash -e

PATH=$PATH:/usr/local/bin

cd /rails || exit

echo "CRON: ${@}"
exec "${@}"


Make sure to chmod +x /bin/cron_executor


Create a dummy rake task for testing

I want to see the impact of my changes right away so I came up with this very simple rake task:

namespace :test do
  task log: :environment do
      puts "[#{DateTime.now}]\tRunning from Cron!"
  end
end



Modify whenever's config/schedule.rb

You have to modify the output so whenever correctly shows the logs if you're running this on Docker, and you also need to modify how rake tasks are executed to use our previously created cron_executor script:

# Sending output to STDOUT for Docker
set :output, { standard: '/proc/1/fd/1', error: '/proc/1/fd/2' }
# TODO: Modify whenever's other job_types as needed, for now I'm only using rake
job_type :rake, "/rails/bin/cron_executor bundle exec rake :task :output"

every 3.minutes do
  rake "test:log"
end


Modify Kamal deploy to use cron

This is the trickiest part of the process and it takes care of:
  • Setting the root user's crontab using whenever (bundle exec whenever --update-crontab). This is extremely convenient since whenever is very readable.
  • Copies the environment variables into /etc/environment (env > /etc/environment). This steps is necessary because as the Kamal documentation states, cron doesn't get your full environment so this is a way to inject it
  • Runs cron in the foreground (cron -f)
  • Runs the container as root (As stated in servers -> options -> user)

servers:
  cron:
    hosts:
    - cron-host.demoapp.com
    cmd: bash -c "bundle exec whenever --update-crontab && env > /etc/environment && cron -f"
    options:
      user: root


See everything working

Deploy your Rails application and after a couple of minutes you should see something like this on your cron server's logs (kamal app logs -r cron -f)

2025-01-20T23:30:01.577774776Z CRON: bundle exec rake test:log
2025-01-20T23:30:04.981673616Z [2025-01-20T23:30:03+00:00]	Running from Cron!
2025-01-20T23:32:01.707032800Z CRON: bundle exec rake test:log
2025-01-20T23:32:05.105227021Z [2025-01-20T23:32:04+00:00]	Running from Cron!
2025-01-20T23:34:01.827464354Z CRON: bundle exec rake test:log
2025-01-20T23:34:05.245636145Z [2025-01-20T23:34:04+00:00]	Running from Cron!
2025-01-20T23:36:01.975579684Z CRON: bundle exec rake test:log
2025-01-20T23:36:05.351346327Z [2025-01-20T23:36:04+00:00]	Running from Cron!


If you get output similar to this you're past the hurdles and ready to use cron via Kamal with confidence.


References



Alan

About Alan Morales

www.alanthoughts.gov/www\alanthoughts. Check it out