Laravel Skyline vs Deck
Deck records what your jobs did, in your database. Skyline changes what your queues do, in Redis. They answer different questions.
Updated · Boring Observability
[ the bottom line ]
These are not really alternatives. Deck answers "what has this job class been doing?" by keeping durable history in your database. Skyline answers "what do I do about this queue right now?" by replacing Horizon itself. If your recurring problem is losing job history to Redis retention, Deck is the closer fit — and it is free. If it is being unable to act on a live queue without SSH, that is Skyline.
- Deck
- A second dashboard beside Horizon that writes a durable job-execution log to your database. Free, MIT (optional paid Deck Cloud)
- Laravel Skyline
- A commercial fork of Laravel Horizon that replaces the package and adds production queue operations. Annual subscription · MIT-licensed source
Deck and Laravel Skyline are both aimed at people running Laravel
queues in production who have found Horizon's dashboard insufficient — but they diverge immediately after that, and
a shortlist that treats them as substitutes will pick the wrong one. Deck is explicit about this in its own
documentation: "Deck does not replace Horizon. Keep php artisan horizon in production." It
installs beside Horizon, listens to queue events, and writes a durable execution log to your database, served from
its own dashboard at /deck. Skyline replaces the laravel/horizon package itself and works
on live Redis state from the dashboard at /horizon.
Put plainly: Deck is a record of what happened. Skyline is a set of controls for what is happening.
Two different layers#
Horizon's job history lives in Redis, governed by the trim retention settings in
config/horizon.php. Those windows are measured in minutes and hours, because Redis is expensive memory
rather than cheap disk. That produces the gap Deck exists to close: a completed job ages out, and with it any
ability to answer "when did ProcessInvoice last succeed, and how long has it been taking?" — a question
that arrives days after the evidence has expired.
Deck answers it by writing every execution to a table in your database, indexed by job class, queue, connection and
tag, with retention you control and a deck:prune command to enforce it. That is a genuinely different
thing from what Skyline does, and Skyline does not do it.
Skyline's gap is the other one. Horizon can show you that a queue has 40,000 jobs in it and cannot show you what they are; it can tell you a job is retrying and cannot run it now; it can pause every worker and cannot pause the one queue that is misbehaving. Those are all operations on live Redis state, which is reachable only from inside the Horizon package — which is why Skyline is a fork rather than a companion.
At a glance#
| Capability | Deck | Skyline |
|---|---|---|
Relationship to laravel/horizon |
Installs alongside it | Replaces it |
| Dashboard | A second one, at /deck |
Horizon's, at /horizon |
| Durable job history beyond Redis retention | Yes — a table in your database | No — Horizon's Redis retention |
| Per job-class stats (avg, p50, p95, failure rate) | Yes | No — aggregate metrics only |
| Stop a job that is already running | Cooperative cancel, needs job-class changes | No — reserved jobs are refused |
| Block a job class at dispatch during an incident | Yes | No |
| In-progress job progress reporting | Yes | No |
| Stale-job & unprocessed-queue alerts | Yes, scheduled | No — Horizon's long-wait notifications |
| Pause & resume a single queue | No | Yes |
| Run a delayed or retrying job immediately | No | Perform Now |
| Read the jobs waiting inside a queue, in order | No | Per-queue drill-down |
| Clear pending jobs from a queue | Yes, from the Workers page | Yes, per job or whole queue |
| Weighted queue processing | No | Yes |
| Front-of-queue dispatch | No | onFront() |
| Database migrations | Yes — the log is the product | None |
| Requirements | PHP 8.3+, Laravel 11–13, a database | PHP 8.2+, Laravel 11+ |
| Price | Free, MIT — optional paid Deck Cloud | Annual subscription |
Stopping a job: two different meanings#
Both tools claim something like "stop this job", and they mean genuinely different things. This is the single comparison most worth reading carefully, because the words are the same and the operations are not.
Deck cancels a running job, cooperatively, if you prepared it. You add Deck's
Cancellable middleware to the job class and call JobCancellation::throwIfCancelled() at
checkpoints inside handle(). Deck then sets a flag and the job bails out at its next checkpoint. Deck
is candid that no PHP worker is force-killed and that pending cancellation on Redis queues is best-effort. The
trade-off is real and reasonable: it can interrupt work already underway, but only in jobs you have edited to be
interruptible, and only at the granularity of the checkpoints you wrote.
Skyline removes a job that has not started. It needs no changes to your job classes, and it
deliberately refuses to touch a job a worker has already reserved — that request is rejected rather than left to
produce a half-executed job. If a worker picks the job up between your click and the delete landing, the request
fails visibly instead of silently doing nothing. In exchange, Skyline releases the job's
ShouldBeUnique lock on the way out, which is
the failure mode that makes deleting jobs from a dashboard dangerous in the first place.
Deck's class blocking has no equivalent in Skyline at all. Blocking
SyncInventory::class for an hour while an upstream API is down, and having every dispatch intercepted
rather than piling into a queue, is a genuinely useful incident tool that Skyline's per-queue pause only
approximates — pausing a queue stops workers consuming it, but producers keep dispatching into it, so the backlog
still grows.
Where Deck is the better answer#
- Your history keeps evaporating. If the recurring pain is that Horizon has already trimmed the job you need to look at, the fix is a database, and Deck is a well-built one — for free.
- You need per-class SLOs. Avg, p50, p95 and failure rate per job class, over a configurable window, is not something Skyline reports.
- You want to be told, not to look. Deck's stale-job and unprocessed-queue alerts run on a schedule and notify. Skyline inherits only Horizon's long-wait notifications.
- You run many services. Every Deck row is scoped by project and environment, and Deck Cloud exists to aggregate them. Skyline is per-application.
- Budget is the constraint. Deck is MIT and free. That is not a small point and we are not going to pretend otherwise.
Where Skyline is the better answer#
- The queue is on fire right now. Pausing one queue, reading what is actually inside it, and draining it are live-state operations on Redis. Deck's dashboard is a record; it cannot reorder or unblock the queue it is describing.
- You have deployed the fix and want the retrying job to go now. Perform Now skips the backoff window. Nothing in Deck's feature set does this.
- Your priority scheme starves a queue. Weighted processing and front-of-queue dispatch live in the worker loop, which only a package that replaces Horizon can reach.
- You cannot take on new schema. Skyline adds no tables, no migrations and no pruning job. Deck's execution log grows with your throughput and is deliberately a database you now operate.
-
You want one dashboard. Deck is a second URL, a second mental model and a second set of
access controls to reason about. Skyline is the
/horizonyour team already opens.
Can you run both?#
By design, nothing puts them in conflict. Deck mounts its own dashboard at /deck and records queue
events through Laravel's own event system; Skyline is the Horizon install at /horizon; and because
Skyline declares replace: laravel/horizon in Composer, Deck's Horizon requirement resolves against it
the same way it would against upstream. Deck's failed-execution rows even link back into Horizon's failed-job store,
which Skyline keeps unchanged.
Skyline is still pre-release, and we have not run the pairing in anger. Treat the above as the design intent of both packages rather than a tested guarantee, and try it on a staging environment first. If you do run them together, we would like to hear how it went.
Choosing between them#
Ask which sentence you have said out loud more often in the last six months.
- "I can't tell when this job last ran successfully." — That is retention, and that is Deck.
- "I need to stop this one queue without stopping everything, and I need to do it now." — That is live control, and that is Skyline.
If both, they are not mutually exclusive, and Deck costs nothing to add.
Common questions
Is Deck an alternative to Laravel Skyline?
Only partly. Deck is explicit that it does not replace Horizon — it installs beside it, listens to queue events and writes a durable execution log to your database, served from its own dashboard at /deck. Skyline replaces the laravel/horizon package itself and works on live Redis queue state from the dashboard at /horizon. The overlap is small: both can search job history and clear pending jobs from a queue. Everything else the two do is at a different layer.
Can I run Deck and Skyline at the same time?
By design, nothing puts them in conflict: Deck mounts its own dashboard at /deck and records queue events, while Skyline is the Horizon install at /horizon, and Deck's Horizon requirement resolves against Skyline's Composer replace declaration. Because Skyline is still pre-release we have not certified that pairing, so treat it as expected rather than guaranteed until we have.
What is the difference between Deck's cooperative cancellation and deleting a job in Skyline?
They act at opposite ends of a job's life. Deck can stop a job that is already running, but only if you have prepared it: you add its Cancellable middleware and call JobCancellation::throwIfCancelled() at checkpoints inside handle(), and cancellation is cooperative — no worker is force-killed. Skyline deletes jobs that have not started yet, needs no changes to your job classes, and explicitly refuses to delete a reserved job rather than leave a half-executed one behind.
Does Laravel Skyline keep durable job history like Deck does?
No. Skyline reads the same Redis-backed history Horizon does, governed by the same trim retention settings, so it inherits the same horizon: once a completed job ages out it is gone. Skyline adds structured job lifecycle logging to a log channel of your choice, which is a stream rather than a queryable table. If you need to answer "when did ProcessInvoice last succeed?" three weeks later, that is a database, and that is what Deck is for.
Does Deck add database tables?
Yes — a durable execution log is the point of it, so Deck ships migrations, grows a table proportional to your job throughput, and provides a deck:prune command to enforce its retention window. Skyline adds no schema and no migrations at all, which is also why it cannot offer durable history.