Skyline

Horizon tells you what your queues are doing. Skyline lets you do something about it.

Horizon's pending list is every queue at once, fifty rows to a page. When default is 40,000 deep, the hundred jobs stuck behind it on high are in there somewhere, and the only way to narrow the list is a tag you were already monitoring. Skyline gives each queue its own list, searchable by job class or argument value.

We run our own production queues on it. 120,000+ jobs a day, sixteen releases since May.

Live demo

Real jobs, running right now.

These are our own production queues, not a recording. The dashboard behind them is open at /horizon with no signup, so you can open a queue and read the jobs waiting inside it before you decide anything.

php artisan horizon live
Connecting to the feed…

Tailing php artisan horizon stdout · polled every 1.5s · newest at the bottom

One JSON object per job event (horizon:work --json), interleaved with the lifecycle records Skyline routes through horizon.log_channel — reserved, released with its reason, migrated, timed out. That second stream does not exist in vanilla Horizon.

Drop-in

Replaces laravel/horizon and keeps the Laravel\Horizon namespace

/horizon

Same dashboard route, same config/horizon.php, no migration

0 changes

No application code edits required to switch over

Redis

Built on Horizon 5.x for production Redis queues

Kept current with Horizon. Skyline is a rebased fork, not a divergent one: every upstream release is merged within 14 days, so you never get stranded on an old version.

Horizon's dashboard is a very good window.

This is the same window with the controls fitted. Everything below runs against the config/horizon.php you already have, from the dashboard, without SSHing into a box or typing Redis commands by hand.

The Skyline dashboard's Current Workload table, with Pause and Empty buttons on the fast and slow queues.
Pause and Empty, per queue, in the Current Workload table. Horizon reaches the same place with horizon:pause-supervisor on the box, and stops every queue that supervisor serves.

Weighted queues

With balance => false a worker serves its queues in strict left-to-right order, so once you run twenty of them the ones at the end never get looked at. A queueWeights map draws each queue with probability proportional to its weight instead. At 3:2:1 the high queue gets checked first about half the time, and low still drains.

Run a retrying job now

You have shipped the fix and the job is sitting in the delayed set waiting out a forty-minute backoff. Open it, press Perform Now, and a worker picks it up. Same for anything scheduled.

Scheduled and retries, told apart

Horizon puts both in one delayed list, so a queue that is quietly failing and backing off looks exactly like one with healthy scheduled work in it. Skyline splits them on attempt count: never attempted is scheduled, released after a failure is a retry.

Pause one queue

A downstream API starts failing and one queue is the problem. horizon:pause stops every worker you have. Skyline pauses that one queue from the dashboard and leaves the rest processing, and jobs accumulate until you resume it.

Read the jobs, and search them

Open a queue and read the jobs waiting inside it, in order, with the arguments each was dispatched with. One search box matches the class name, an argument name or an argument value: type "checkout_id: 3" to find the jobs holding it.

Delete one job, or empty the queue

Both release the laravel_unique_job:* locks on the way out. Horizon holds them until the TTL expires, which silently discards every ShouldBeUnique dispatch in the meantime. That is Horizon issue #1678, open since December 2025.

Metrics you can read

Horizon renders a chart per job class and a chart per queue. At ten queues and forty job classes that is fifty sparklines and no answer. Skyline aggregates them into charts that fit on one screen.

Prometheus and Grafana

Every measurement behind the dashboard is exported to Prometheus, so the numbers you watch during an incident are the ones your alerts fire on. A Grafana dashboard covering all of it ships with the package.

The jobs waiting inside the slow queue, each with the argument it was dispatched with, above a search box.
The jobs inside a queue, with their arguments.
Scheduled jobs listed with a Perform Now control on each row.
Perform Now, instead of waiting out the backoff.
Throughput and runtime charts aggregated across all queues.
Throughput and runtime, aggregated.
config/horizon.php
'supervisor-1' => [
    'connection' => 'redis',
    'queue'      => ['high', 'default', 'low'],
    'balance'    => false,
    'queueWeights' => [
        'high'    => 3,
        'default' => 2,
        // 'low' omitted, so it keeps a weight of 1
    ],
],

Give a queue more attention without starving the rest.

With balance => false, a worker drains high completely before it looks at low. That is fine until high is never empty, at which point everything under it starves.

A queueWeights map replaces that with a proportional draw. On every poll the worker builds a fresh ordering, picking queues without replacement with probability proportional to their weight.

high
×3 · 50%
default
×2 · 33%
low
×1 · 17%

How often each queue is checked first. Applies when balance => false.

Everything Horizon does, plus the buttons.

Capability Horizon Skyline
Drop-in for laravel/horizon (same config & namespaces)
Throughput, runtime & failure metrics
Pause and resume a single queue Whole supervisor, from the CLI
Scheduled jobs and retries told apart One delayed list
Run a delayed or retrying job immediately Wait out the backoff
Proportional queue priority without starvation Strict priority only
Browse the jobs waiting on one queue Every queue in one list
Delete a job or empty a queue from the UI Redis keys, by hand
Unique job lock released on out-of-band removal Lock wedges until TTL
Search jobs by class name or argument value Tags, registered in advance
Prometheus endpoint and Grafana dashboard Dashboard only

None of these are hypothetical. Every gap in that column has a request behind it on Horizon's own tracker: issues #1445, #1434, #1225, #1678 and #1310, variously closed, declined or still open.

Comparing Skyline with something else? We wrote the long version for each, including where they win.

When Horizon on its own is fine.

We would rather you did not buy this than bought it and found it changed nothing. Two things make Skyline pointless:

  • One or two queues, and nothing ever backs up behind anything else. Weighted queues solve a problem you do not have.

  • You need to know how deep a queue is, never what is inside it. Horizon's dashboard already answers that.

If both hold, Horizon on its own is the right call. The day one of them stops being true, switching is a single composer require.

Three commands to switch.

Because Skyline replaces laravel/horizon, there are no code changes and no migration.

  1. 01

    Authenticate Composer

    Point Composer at the private registry with your Anystack license key.

    composer config --global \
      --auth http-basic.laravel-skyline.composer.sh \
      you@example.com YOUR-LICENSE-KEY
  2. 02

    Require the package

    Add the registry to composer.json, then pull in Skyline. It replaces laravel/horizon.

    composer require \
      boring-o11y/laravel-skyline
  3. 03

    Install and run

    Publish assets and start the dashboard and workers, exactly as before.

    php artisan horizon:install
    php artisan horizon

The dashboard mounts at /horizon and config/horizon.php is identical to upstream, so your existing service-provider customizations and gate definitions keep working.

Read the docs →

MIT-licensed · Every version you buy is yours forever · No runtime license check

One payment. $99.

One license covers every application your team runs it on and includes a year of upgrades. What you receive in that year is yours permanently, whether or not you ever renew.

Launch price

$99 one time
one license

No per-app fee, no seats. Goes to $139 at general availability.

  • Twelve months of upgrades, upstream Horizon merged within 14 days
  • Every version from that year, yours to reinstall forever
  • No runtime license check, so an expired year cannot stop your queues
  • 30 days to change your mind, refunded in full

Secure checkout by Anystack, with cards and VAT invoices.

Frequently asked questions

What is Skyline for Laravel?

+

Skyline is a commercial fork of Laravel Horizon, maintained by Boring Observability. It keeps everything Horizon does for monitoring Redis queues and adds the operations layer Horizon leaves out: weighted queues, pausing a single queue, running a scheduled or retrying job immediately, reading and searching the jobs inside a queue, deleting them, and a metrics dashboard that aggregates instead of drawing one chart per job class.

Is Skyline for Laravel a drop-in replacement for Laravel Horizon?

+

Yes. Skyline keeps the Laravel\Horizon\ namespace and "replaces" laravel/horizon in Composer, so your existing config/horizon.php, service-provider customizations, gate definitions and Artisan commands all keep working. The dashboard still mounts at /horizon. There are no code changes and no migrations.

Will Skyline keep up with Laravel Horizon?

+

Yes. Skyline is a rebased fork, not a divergent one: main is always a recent upstream Horizon tag with the Skyline commits replayed on top, and every upstream release is merged within 14 days. So you get new Horizon versions, bug fixes and Laravel compatibility updates without being stranded on an old base. We run Skyline against our own production queues every day, which is why keeping current is part of how we operate rather than an afterthought.

How is Skyline different from Laravel Nightwatch or an APM?

+

Skyline is queue control, not telemetry. An APM tells you something is wrong. Skyline is the layer that lets you do something about it: pause the queue, run the retry now, drain the backlog. Pair it with whatever you already use for observability. Nothing overlaps and nothing has to be replaced.

Is Skyline safe to run in production, and what data leaves my servers?

+

None of it. Skyline is a package that runs inside your application against your own Redis, exactly like Horizon: no agent, no external service, no telemetry. Your job payloads and queue metrics never leave your infrastructure. The license key is used only by Composer when installing or updating the package, so there is no runtime license check that could phone home or interrupt job processing. Dashboard access is governed by your existing viewHorizon gate.

How is Skyline licensed and priced, and what happens after twelve months?

+

Skyline is a one-time purchase of $99, on sale now at that launch price instead of $139. One license covers every application your team runs it on, with no per-app fee and no seat count. The purchase includes twelve months of upgrades: every Skyline release and every upstream Horizon merge published in that year. When the twelve months end nothing stops working. Every version you received stays yours, keeps running, and can still be pulled from the private registry when you rebuild. There is no runtime license check, so an expired upgrade window is a build-time consideration at worst and never a production one. The only thing you lose is access to releases published after your year, until you renew. Renewing is optional and buys the next twelve months at the rate you bought at. The source is MIT; what you are buying is registry access and the maintenance behind it.

What if Skyline is not a fit? Do you offer a refund?

+

Yes. Every purchase comes with a 30-day money-back guarantee. Email tech@boring-observability.dev within 30 days of being charged and we refund it in full, no questions asked and no call to sit through. Rolling back to Horizon is a single Composer command with no migration to unwind, so trying Skyline costs you a deploy either way.

What support do I get when I buy Skyline?

+

Email support at tech@boring-observability.dev, straight to the people who build and run Skyline. There is no support tier and no ticket queue in between. We aim to reply within one business day, and security reports are triaged the same day.

How do I install Skyline, and can I switch back to Horizon?

+

Authenticate Composer with your license key, add the laravel-skyline.composer.sh registry to composer.json, then run composer require boring-o11y/laravel-skyline, php artisan horizon:install and php artisan horizon. Switching back is composer remove boring-o11y/laravel-skyline followed by composer require laravel/horizon. Skyline stores its extra tracking data under the same Redis prefix and adds no schema, so there is no migration to unwind and nothing to export.

What are weighted queues?

+

In a supervisor running with balance => false, workers serve queues in strict left-to-right priority: the first queue is drained completely before the second is looked at. That works until the first queue is never empty, at which point everything below it starves. A queueWeights map replaces that with a proportional draw. On every poll the worker builds a fresh ordering, drawing queues without replacement with probability proportional to their weight, so at 3:2:1 the high queue is checked first about 50% of the time, default about 33% and low about 17%. Queues without an entry default to a weight of 1.

Does Skyline work with non-Redis queue drivers?

+

Skyline monitors Redis queues, just like Horizon. Features that rely on Redis list semantics do not apply to the sync or database drivers.

Buy Skyline.

Checkout takes a minute and ends with your license key. Point Composer at the private registry and the dashboard you already know gains the buttons it was missing.

Buy Skyline for $99

Secure checkout by Anystack. 30 days to change your mind.

Questions first? Email us and one of the people who builds it will answer.