Active Job Rails Cron Job

I have an active job that I want to run once a week at a specific time. Is there a recommended way to achieve this with fly.io? The documentation around this topic is pretty scattered: Some mentions of fly machines and some cron/queues discussion for Laravel.

How would cron jobs work with Fly instances scaling to 0?

Thanks for any pointers!

To accomplished this via scale to zero, New feature: Scheduled machines is a good place to get started, but you can’t run it at a specific time.

If you want to run at a specific time you’ll have to keep a machine running that runs something like superchronic as outlined at Recurring scheduled tasks (like cron) - #25 by Brad, but you can’t achieve “scale to zero”.

There’s no perfect answer at the moment, but at least you can choose between the trade-offs of cost vs accuracy.

Thanks for the response!

My use case seems pretty basic: periodically (once a week) execute a Rails Active Job which runs some database queries and potentially sends out emails.

If giving upscaling to zero can make my life easier, I am happy to take that approach.

Fly Machines sound interesting and could work if the spun-up machine can trigger a rake task on the machine hosting my rails app. Is there a built-in mechanism for that or would I need to implement an API endpoint with authentication?

Supercronic on the surface sounds easy as it could run on the same server as another process, correct?

Could you recommend which one is easier to set up for my purpose?

Many thanks!

You could run superchronic on your web server as a separate process, but you’ll run into problems if you scale up your machines to more than one instance since you’d then be running multiple instances of superchronic.

I setup Recurring scheduled tasks (like cron) - #25 by Brad for my SaaS website, https://legiblenews.com, which sends out an email once per day or per week depending on the users’ subscription settings. It runs one, and only one, superchronic machine instance and the website is running on 2 machine instances.

You could trigger a URL from something like How to set up reoccurring jobs with GitHub Actions if you want to jump through the hoops of setting up auth, etc. for the service and are OK with Github actions going down, not being exactly on-time, etc.

Quick and dirty approach.

create Procfile.fly:

app: bin/rails server -p 3000
cron: cron -f

create config/cron:

RUN crontab -l | \
    { cat; echo "* * * * * touch /rails/tmp/clock"; } | \
    crontab -

Regenerate dockerfile:

bundle update dockerfile-rails
bin/rails generate dockerfile --root --add cron \
  --procfile=Procfile.fly --instructions=config/cron

Note that this will run your Rails application as root and will run cron in each machine instance.

Sorry for the late response folks, but got everything working. Many thanks for your help!

My final solution ended up using:

  • supercronic by following Recurring scheduled tasks (like cron). I execute my Active Job as a rake task that I had to define.

  • Using Rails Active Job along with the “inline” queue backend to execute the task immediately. I would like to use something like Sidekiq down the line, but I didn’t want to fuss around with it and I wasn’t sure if I would need to run that in yet another process. The inline backend will probably work fine for a while.

  • Some docs for setting up the .toml file were a little unclear to me. Especially, it wasn’t clear how to migrate from using the default [http_service] to [[services]]. Some trial and error later, this worked:

app = "campusninja"
primary_region = "ams"
console_command = "/rails/bin/rails console"

[processes]
  web = "./bin/rails server"
  cron = "supercronic ./crontab"

[[statics]]
  guest_path = "/rails/public"
  url_prefix = "/"

[[services]]
  processes = ["web"] # this service only applies to the web block
  http_checks = []
  internal_port = 3000
  protocol = "tcp"
  script_checks = []

  [services.concurrency]
    hard_limit = 25
    soft_limit = 20
    type = "connections"

  [[services.ports]]
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443

A plug-and-play solution would be really cool, but this also works. Thanks!

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.