One-off scripts on Heroku with Rails
Every so often you have a small script that needs to be run on your staging or production server, but committing and deploying the script on its own is just too much of a hassle. Luckily Rails and Heroku has a great solution for this.
rails runner
If you have a script in your application that needs to run in the context of your Rails application, you can run it via rails runner
.
For example, let’s say we have this very important script:
puts Rails.env
Running this with just ruby
gives an error:
$ ruby hi.rb
hi.rb:1:in `<main>': uninitialized constant Rails (NameError)
puts Rails.env
^^^^^
This makes sense, since Rails
isn’t loaded. That’s where rails runner
steps up and ensures the full Rails environment is loaded:
$ rails runner hi.rb
development
Pretty helpful!
rails runner
doesn’t only run scripts from disk, it also has a few other tricks up its sleeve. For example, you could pass the code directly:
$ rails runner "puts Rails.env"
development
or you can pipe the Ruby code to rails runner
- note the little -
at the end of the command there, that means read the script from stdin.
$ cat hi.rb | rails runner
development
heroku run
Heroku’s CLI has a function similar to rails runner
, namely heroku run
.
Usage: heroku run COMMAND
Example: heroku run bash
That allows you to run a one-off process inside a Heroku dyno. Say, if we needed to run migrations on Heroku we could do
$ heroku run rails db:migrate
Also pretty helpful!
heroku run rails runner
Let’s combine those! heroku run
runs any process, what if that process was rails runner
?
$ heroku run 'rails runner "puts Rails.env"'
We do have to be fairly vigilant about placing our quotation marks so the correct things are run by the correct processes. In the above everything in '
is run by heroku run
, which in turn runs everything in "
using rails runner
.
heroku run rails runner with a pipe
But can we pipe a script to rails runner
and have heroku run
run it in a dyno? I am so glad you asked! Yes, we can. heroku run
forwards stdin to the process being run, so we can do
$ echo "puts Rails.env" | heroku run "rails runner -"
Running rails runner - on ⬢ some-app... up, run.7413
puts Rails.env
production
Note how the script/stdin is echoed back to us, which gets annoying for longer scripts. To avoid this we could use the --no-tty
flag:
$ echo "puts Rails.env" | heroku run --no-tty "rails runner -"
Running rails runner - on ⬢ some-app... up, run.9354
production
How to run one-off Rails application scripts on Heroku
This leads us to the natural conclusion of all the above. In order to run a one-off script in a Heroku dyno with your Rails application environment loaded without having to add it to git and deploy, you can run it like so:
$ cat hi.rb | heroku run --no-tty "rails runner -"
Running rails runner - on ⬢ some-app... up, run.1846
production
Very helpful indeed!