Background jobs fail silently. Horizon shows you what's running right now, but the history disappears with Redis. Deck remembers everything — every execution, searchable, with controls to cancel runaway jobs and block broken classes during incidents. Free open source package, hosted Cloud dashboard available.
No reviews yetBe the first to leave a review for Deck
Maker
📌
Hey Product Hunt 👋
I built Deck because I kept running into the same problem at work: a Laravel job would silently stop running, and I'd find out from a customer instead of a dashboard. Horizon showed me what was happening right now, but the history disappeared with Redis.
I started building this about two months ago, ran it in production quietly for a few weeks first to make sure it actually held up, then open sourced the package and launched Deck Cloud alongside it. It's been in production for about 7 weeks now, just crossed 2 million executions processed, and last week I had to fix a real scaling issue live after a feature I shipped put too much load on the database. Building this has been equal parts satisfying and humbling.
Deck adds a durable, searchable execution log on top of Horizon, plus a few things I always wanted during an incident: cancel a runaway job cleanly, block a broken job class without redeploying, get alerted when something that should run hourly just... doesn't.
The package is open source and free forever: composer require deck/deck. Deck Cloud is the hosted version if you want one dashboard across multiple apps and environments, 7-day free trial, no credit card required to start.
Happy to answer anything, architecture, why I built it this way, what's next on the roadmap. Would genuinely love to hear if you've hit the same "job silently died and nobody knew" problem.
Report
Does Deck integrate with existing Horizon dashboards or does it fully replace them, and what happens to historical data when you first install it?
Report
Maker
@kezban540728 Great question! Deck doesn’t replace Horizon at all, it runs alongside it. You keep php artisan horizon running exactly as before, Deck just adds a separate dashboard at /deck that tracks things Horizon doesn’t: durable execution history, per-class last-run status, cooperative cancellation, stale-job alerts.
On historical data: Deck only starts recording from the moment you install it, it doesn’t backfill from Horizon’s Redis history since that’s typically already expired or very short-lived by the time you’d install something like this. So no historical import, but going forward everything’s captured and kept for 90 days by default (self-hosted) or based on your retention settings on Cloud.
Basically: Horizon for real-time worker supervision, Deck for the memory Horizon doesn’t have.
Report
How does Deck handle retention if Redis itself is the bottleneck causing the failures in the first place, and does the cloud version have any storage limits on the job history?
Report
Maker
@abdulkadir70837 Good question. Deck's execution log lives in your own database (Postgres/MySQL), not Redis, so it's fully independent of whatever's happening on the Redis side. If Redis itself is struggling, queue delays, memory pressure, connection issues, Deck still has visibility into whatever job data made it through, since recording happens at the worker level via Laravel's queue events, not by reading Redis internals.
That said, if Redis is bad enough that jobs aren't being dispatched or picked up at all, there's nothing to record yet, Deck can only see executions, it can't see what never got queued in the first place. That's actually one of the harder problems in this space: a job that's never dispatched can't be detected as "missing" the same way a job that started and stalled can be.
On Cloud storage limits: retention is tied to your plan, 90 days by default on self-hosted, configurable on Cloud based on your team's settings, with the ability to extend it. No hard cap on volume beyond the execution limits tied to pricing tiers.
Report
How does Deck handle the storage growth over time as execution history piles up, is there any built-in pruning or do I need to manage retention myself?
Report
Maker
@nebahatibcr There's built-in pruning, you don't have to manage it manually. Self-hosted, there's a deck:prune artisan command that deletes execution rows older than your configured retention_days (90 by default). You schedule it yourself, typically daily via Laravel's scheduler:
php
Schedule::command('deck:prune')->daily();
On Cloud, pruning happens automatically based on your team's retention settings, nothing to configure or run yourself.
One thing worth knowing if you're at real volume: for busy apps we recommend a dedicated database connection for Deck's tables, since it's writing a row per job execution, keeping that separate from your primary app database avoids adding pressure there as history accumulates.
Report
Curious how the cancel and block controls actually work under the hood — does Deck need to wrap job dispatching, or can it intercept runs that were already dispatched before Deck was added?
Report
Maker
@memetlepc No, Deck does not need to wrap job dispatching to cancel or block. The cancel-a-running-job and block-a-class controls work by interception at execution time — the worker checks a flag right before/while it runs each job. That means jobs that were already sitting on the queue before Deck was installed get caught the moment a Deck-equipped worker picks them up. The one capability that leans on dispatch-time involvement is removing a job that's still queued (cancel_pending), because that one needs the job's UUID to find it in the queue.
Report
finally someone looked at horizon and said what we've all been thinking. the "block broken classes during incidents" detail is such a quiet lifesaver, exactly the kind of thing you only think about after a 3am outage.
Report
Maker
@ogluzafer19806 Ha, exactly the origin story. That specific feature came out of one incident where I really wished I could just tell a job class "stop trying for the next hour" without touching a deploy pipeline at 3am. Once you've lived through that once, you build the button.
Appreciate you noticing it, it's easy to undersell in a feature list but it's honestly the one I use most myself.
Report
Finally someone fixed the "what ran last Tuesday at 3am" mystery. The search through old job executions already saved me from digging through logs during an incident.
Does Deck integrate with existing Horizon dashboards or does it fully replace them, and what happens to historical data when you first install it?
How does Deck handle retention if Redis itself is the bottleneck causing the failures in the first place, and does the cloud version have any storage limits on the job history?
@abdulkadir70837 Good question. Deck's execution log lives in your own database (Postgres/MySQL), not Redis, so it's fully independent of whatever's happening on the Redis side. If Redis itself is struggling, queue delays, memory pressure, connection issues, Deck still has visibility into whatever job data made it through, since recording happens at the worker level via Laravel's queue events, not by reading Redis internals.
That said, if Redis is bad enough that jobs aren't being dispatched or picked up at all, there's nothing to record yet, Deck can only see executions, it can't see what never got queued in the first place. That's actually one of the harder problems in this space: a job that's never dispatched can't be detected as "missing" the same way a job that started and stalled can be.
On Cloud storage limits: retention is tied to your plan, 90 days by default on self-hosted, configurable on Cloud based on your team's settings, with the ability to extend it. No hard cap on volume beyond the execution limits tied to pricing tiers.
How does Deck handle the storage growth over time as execution history piles up, is there any built-in pruning or do I need to manage retention myself?
@nebahatibcr There's built-in pruning, you don't have to manage it manually. Self-hosted, there's a deck:prune artisan command that deletes execution rows older than your configured retention_days (90 by default). You schedule it yourself, typically daily via Laravel's scheduler:
php
On Cloud, pruning happens automatically based on your team's retention settings, nothing to configure or run yourself.
One thing worth knowing if you're at real volume: for busy apps we recommend a dedicated database connection for Deck's tables, since it's writing a row per job execution, keeping that separate from your primary app database avoids adding pressure there as history accumulates.
Curious how the cancel and block controls actually work under the hood — does Deck need to wrap job dispatching, or can it intercept runs that were already dispatched before Deck was added?
@memetlepc No, Deck does not need to wrap job dispatching to cancel or block. The cancel-a-running-job and block-a-class controls work by interception at execution time — the worker checks a flag right before/while it runs each job. That means jobs that were already sitting on the queue before Deck was installed get caught the moment a Deck-equipped worker picks them up. The one capability that leans on dispatch-time involvement is removing a job that's still queued (cancel_pending), because that one needs the job's UUID to find it in the queue.
finally someone looked at horizon and said what we've all been thinking. the "block broken classes during incidents" detail is such a quiet lifesaver, exactly the kind of thing you only think about after a 3am outage.
@ogluzafer19806 Ha, exactly the origin story. That specific feature came out of one incident where I really wished I could just tell a job class "stop trying for the next hour" without touching a deploy pipeline at 3am. Once you've lived through that once, you build the button.
Appreciate you noticing it, it's easy to undersell in a feature list but it's honestly the one I use most myself.
Finally someone fixed the "what ran last Tuesday at 3am" mystery. The search through old job executions already saved me from digging through logs during an incident.