Michael Trojanek (relativkreativ) — Bootstrapper and creator of things

This article was published on August 25th 2014 and takes about 3 minutes to read.

Use it with caution — it is probably still valid, but it has not been updated for over a year.

Running Ruby scripts from within a cron job in an rbenv environment

Executing a scheduled Ruby script from within a cron job is a bit tricky if you manage your Ruby versions with rbenv. Let me show you how to do it right.

In one of my previous articles I created a small Ruby script to handle subscribers to your newsletter in the background.

The way I executed this script from within my crontab caused a little confusion among some readers, so let me elaborate a bit on why you cannot just run a Ruby script from within a crob job when you are using rbenv (examples are run as app user on my production server).

The cron environment

By adding the crontab entry */1 * * * * env > /tmp/env.txt you'll get a dump of all environment variables into the file /tmp/env.txt every minute. After waiting for a minute, let's take a peek into this file:

...
PATH=/usr/bin:/bin
...

Compare this to the output of env run from inside a normal shell:

...
PATH=/home/app/.rbenv/shims:/home/app/.rbenv/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/app/bin
...

You can see that the $PATH variable (listing all directories where your system looks for executable files) is very stripped down and does not include the directories needed for rbenv to work (adding $HOME/.rbenv/shims and $HOME/.rbenv/bin to your $PATH is done as part of the rbenv installation if you refer to its documentation).

Run the script from within a non-interactive login shell

The solution to this problem is opening a non-interactive login shell from within the cronjob and run the script this way:

*/1 * * * * bash -lc "env > /tmp/env.txt"

The output:

...
PATH=/home/app/.rbenv/shims:/home/app/.rbenv/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/app/bin
...

Now our $PATH is configured the way it needs to be in order for rbenv to take over. In most cases (at least on CentOS/RedHat-based systems) this is sufficient to make the following crontab entry work:

*/1 * * * * bash -lc "ruby script.rb"

Still no luck?!

However, there may be cases where your bash does not reside in /bin but in some other place like /usr/local/bin. Since the user's $PATH will not cover that location, you cannot even open a login shell.

In this case, you have to add this directory to the $PATH manually before opening a login shell. First, find out where your bash command lives by running the command which bash.

Then add this directory to the user's $PATH before opening the login shell from within the cron job:

*/1 * * * * PATH=$PATH:/usr/local/bin && bash -lc "env > /tmp/env.txt"

Creating a wrapper script

If you find yourself in constant need to run scheduled Ruby scripts, it might pay to create a small wrapper script to keep things DRY.

Create a script called rbenv_wrapper in ~/bin (because as we have seen above, this place is always covered within $PATH):

#! /bin/sh

PATH=$PATH:/usr/local/bin
bash -lc $*

After making it executable with chmod +x ~/bin/rbenv_wrapper this script adjusts the $PATH and the runs any command(s) you supply inside a non-interactive login shell.

Now you can run Ruby scripts from within your crontab like this:

*/1 * * * * rbenv_wrapper script1.rb
*/1 * * * * rbenv_wrapper script2.rb > /tmp/output.txt

Expand your DevOps skills!

Join hundreds of Rails developers and operators on my email list and get my ebook Build Your Own Rails Server as a free welcome gift.

No spam — guaranteed. You can leave at any time.