Skyline

Migrating from Laravel Horizon

Swap the Composer package, redeploy, restart the workers. No code changes, and no one-way door.

Migrating from Laravel Horizon to Laravel Skyline needs no application code changes and no migrations. Skyline keeps the Laravel\Horizon\ namespace and declares replace: laravel/horizon in Composer, so every import, gate, config key and Artisan command you already have keeps resolving — against Skyline instead of Horizon. In practice the migration is a Composer swap, a deploy, and a worker restart.

The migration#

  1. Remove laravel/horizon from the require block of composer.json.
  2. Add the Anystack registry and require Skyline — see Installation for the license-key setup.
  3. Run composer update.
  4. Deploy, then restart the workers with php artisan horizon:terminate.
composer remove laravel/horizon
composer config repositories.skyline composer https://laravel-skyline.composer.sh
composer require boring-o11y/laravel-skyline

php artisan horizon:terminate

Composer will refuse to install both packages at once. That is the intended behaviour rather than a conflict to work around — replace is what makes every third-party package that depends on laravel/horizon resolve cleanly against Skyline.

What keeps working, untouched#

  • Your config/horizon.php, copied across unchanged. Every Skyline key has a working default.
  • Your HorizonServiceProvider, including Gate::define('viewHorizon', …) and Horizon::auth().
  • Every use Laravel\Horizon\… import in your application code.
  • Every php artisan horizon:* command, by name and behaviour.
  • The dashboard, still mounted at /horizon.
  • The jobs already sitting in Redis: the payload format does not change, so nothing needs to drain first.
Re-publish the dashboard assets

Skyline ships its own compiled dashboard assets. If your deploy publishes Horizon's assets into public/, add php artisan horizon:publish to it — otherwise you will be serving Horizon's old JavaScript against Skyline's API and wondering why the new tabs are missing.

Deploying without dropping jobs#

The swap is an ordinary deploy, and the ordinary deploy rules apply. Workers run the code they booted with, so a worker started before the deploy keeps running Horizon's code until it is restarted — which is exactly what horizon:terminate is for: it lets each worker finish the job in its hands and then exit, and your process supervisor restarts it on the new code.

There is no window in which jobs are lost. A job dispatched by new code and picked up by a not-yet-restarted worker is an ordinary Redis payload either way; the two versions read and write the same format.

Verifying the migration#

After the workers come back, three things tell you the swap took:

  • composer show boring-o11y/laravel-skyline reports the installed version.
  • The dashboard at /horizon shows the tabbed Jobs view, with Scheduled and Retries as separate tabs.
  • Opening a queue from the dashboard drills into the jobs waiting inside it, rather than only counting them.

If the dashboard looks exactly as it did before, you are almost certainly serving stale published assets — see the note above.

Rolling back#

Rolling back is the same operation in reverse, and it is deliberately cheap:

composer remove boring-o11y/laravel-skyline
composer require laravel/horizon

php artisan horizon:publish
php artisan horizon:terminate

Skyline adds no database schema and stores its extra tracking data under the same Redis prefix Horizon already uses. A rollback therefore loses the Skyline-only views — the trend charts, the delayed-job index, the previous-attempt panel — and leaves your queues, your jobs and your metrics history untouched. The Skyline keys left behind in Redis are trimmed on their own retention schedule and cost nothing.

Config keys are safe to leave in place

A queueWeights map or a worker_output key left in config/horizon.php after a rollback is simply ignored by upstream Horizon, which reads only the keys it knows about. You do not have to strip them out to go back.

If the subscription lapses#

Already-installed versions keep running normally — there is no runtime license check that can interrupt job processing. Only future composer update calls against the private registry fail authentication, until the subscription is renewed. A lapsed license is a build-time problem, never a production one.

Common questions

Do I have to change any code to migrate from Horizon to Skyline?

No. Skyline keeps the Laravel\Horizon\ namespace and replaces laravel/horizon in Composer, so your config/horizon.php, HorizonServiceProvider, viewHorizon gate and every Laravel\Horizon\ import keep working untouched. There are no migrations to run.

Can I roll back from Skyline to Horizon?

Yes, and it is the same operation in reverse: require laravel/horizon again and drop the Skyline package. Skyline adds no database schema and stores its extra tracking data under the same Redis prefix, so a rollback loses the Skyline-only views and leaves your queues and jobs untouched.

Will my queues drain or lose jobs during the migration?

No. The jobs already in Redis are untouched by the swap, because the payload format does not change. Migrating is an ordinary deploy: install the package, redeploy, then run php artisan horizon:terminate so the workers restart on the new code.

Queue control, not just queue monitoring.

Skyline is a drop-in replacement for Laravel Horizon that lets you act on what you see — pause a queue, jump a job to the front, drain a backlog.

Sign up for early access