Running cron on Heroku with Laravel queues and Horizon

Heroku doesn't support the cron. Although there are multiple add-ons available that support crontab syntax and can run tasks in certain intervals, Heroku's custom clock process happened to be the most reliable, and actually pretty simple way to simulate cron on Heroku.

This setup involves:

  1. Creating clock script as Laravel command
  2. Creating jobs and registering them in Laravel schedule
  3. Setting up Laravel queue with Laravel Horizon
  4. Adding Heroku Procfile to the app with clock and worker processes

1. Clock console command #

To simulate crontab on Heroku we'll create a CLI command in app/Console/Commands/ClockRunCommand.php that will run Laravel schedule every minute in the indefinite loop:


namespace App\Console\Commands;

class ClockRunCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/

protected $signature = 'clock:run';

/**
* The console command description.
*
* @var string
*/

protected $description = 'Runs scheduler every minute.';

/**
* Execute the console command.
*
* @return int
*/

public function handle()
{
while (true) {
if (now()->second <= 10) { // runnable window
$this->info('Running schedule...');
$this->call('schedule:run');
sleep(4); // min. duration
}
sleep(7); // polling delay
}

// fail - this command should never return anything
return 1;
}
}

Important: keep in mind that this approach will work only if schedule:run command takes very short time to execute, e.g. all tasks are very quick, ideally < 30s, or asyncronous, otherwise it'll skip some minutes.

In our case, we've had some very long (up to several hours) scripts to execute and ended up wrapping them into jobs that then picked up by the queue to ensure tasks execution and more flexibility with retries and timeouts.

2. Creating jobs and registering them in Laravel schedule #

Instead of using standard crontab syntax we register tasks as jobs in app/Console/Kernel.php:

protected function schedule(Schedule $schedule)
{
$schedule->job(\App\Jobs\FirstJob::class)
->everyTenMinutes();

$schedule->job(\App\Jobs\SecondJob::class)
->dailyAt('22:00');

$schedule->job(\App\Jobs\ThirdJob::class)
->everyMinute();
}

3. Setting up Laravel Horizon #

Laravel Horizon provides visibility of jobs execution and allows queues tuning. In order for Horizon to work we'll need a Redis instanse which can be created using Heroku Redis add-on. Follow official docs to install Horizon.

4. Heroku Procfile #

The Procfile on Heroku allows to start both clock process and Horizon in a worker process on a dyno start.

Procfile is going to have the following commands:

web: vendor/bin/heroku-php-apache2 public/
worker: php artisan horizon
clock: php artisan clock:run