<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Deploynix</title>
    <description>The latest articles on DEV Community by Deploynix (@deploynix).</description>
    <link>https://dev.to/deploynix</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3800435%2Fa1f4de22-651f-46e8-adc8-a9da58944683.png</url>
      <title>DEV Community: Deploynix</title>
      <link>https://dev.to/deploynix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deploynix"/>
    <language>en</language>
    <item>
      <title>Self-Hosted PaaS Showdown 2026: Coolify vs Dokploy vs CapRover vs Deploynix</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sun, 21 Jun 2026 23:58:58 +0000</pubDate>
      <link>https://dev.to/deploynix/self-hosted-paas-showdown-2026-coolify-vs-dokploy-vs-caprover-vs-deploynix-46l3</link>
      <guid>https://dev.to/deploynix/self-hosted-paas-showdown-2026-coolify-vs-dokploy-vs-caprover-vs-deploynix-46l3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Self-hosted PaaS tools exploded in 2026 because cloud bills did.&lt;/strong&gt; With 86% of CIOs in Barclays' late-2024 technology survey planning to repatriate at least some public-cloud workloads — the highest figure on record, &lt;a href="https://www.puppet.com/blog/cloud-repatriation" rel="noopener noreferrer"&gt;per Puppet's analysis&lt;/a&gt; — developers are racing to recreate the Heroku experience on infrastructure they control. Three open-source projects lead that movement: &lt;strong&gt;Coolify&lt;/strong&gt; (~57,300 GitHub stars), &lt;strong&gt;Dokploy&lt;/strong&gt; (~35,000 stars), and &lt;strong&gt;CapRover&lt;/strong&gt; (~15,100 stars), all verified on GitHub as of June 22, 2026. This guide compares them head-to-head, then adds a fourth option built on a different model entirely — &lt;strong&gt;Deploynix&lt;/strong&gt;, a Laravel-first managed control plane for your own servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Coolify is the most feature-complete all-rounder: 280+ one-click services, Docker Compose, native multi-server, and a free self-hosted tier under Apache-2.0.&lt;/li&gt;
&lt;li&gt;Dokploy is the fast-rising lightweight challenger — Docker Swarm + Traefik, clean UI, native Compose — but still pre-1.0 (v0.29.x).&lt;/li&gt;
&lt;li&gt;CapRover is the battle-tested veteran (since 2017): rock-stable, simple, but a dated UI, slower release cadence, and weaker Compose support.&lt;/li&gt;
&lt;li&gt;Deploynix is the odd one out by design: a managed SaaS that provisions native, non-Docker servers on your own DigitalOcean/Hetzner/Vultr/Linode/AWS account — purpose-built for Laravel and PHP, a Forge-style alternative.&lt;/li&gt;
&lt;li&gt;The real decision isn't "which panel" — it's "do I want to run the control plane myself (Coolify/Dokploy/CapRover) or have it managed (Deploynix)?"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The quick verdict: which self-hosted PaaS is right for you?
&lt;/h2&gt;

&lt;p&gt;For most teams in 2026, &lt;strong&gt;Coolify is the safest all-purpose pick&lt;/strong&gt;, Dokploy is best if you want a lean Swarm-native panel, CapRover suits people who value stability over features, and Deploynix fits Laravel/PHP teams who'd rather not babysit Docker or the control plane at all. All three open-source tools are free to self-host under Apache-2.0; Deploynix is a paid managed service that runs on infrastructure you own and pay your cloud provider for directly.&lt;/p&gt;

&lt;p&gt;Tool&lt;/p&gt;

&lt;p&gt;Model&lt;/p&gt;

&lt;p&gt;Best for&lt;/p&gt;

&lt;p&gt;Watch out for&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coolify&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OSS self-hosted panel&lt;/p&gt;

&lt;p&gt;Feature-rich, polyglot, production all-rounder&lt;/p&gt;

&lt;p&gt;You maintain the panel and the server&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dokploy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OSS self-hosted panel&lt;/p&gt;

&lt;p&gt;Lightweight, modern, Swarm-native clustering&lt;/p&gt;

&lt;p&gt;Still pre-1.0; younger ecosystem&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CapRover&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OSS self-hosted panel&lt;/p&gt;

&lt;p&gt;Simplicity and long-term stability&lt;/p&gt;

&lt;p&gt;Dated UI, slow cadence, limited Compose&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploynix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Managed SaaS, your VPS&lt;/p&gt;

&lt;p&gt;Laravel/PHP teams wanting native, zero-maintenance hosting&lt;/p&gt;

&lt;p&gt;Subscription cost; PHP/Laravel focus&lt;/p&gt;

&lt;h2&gt;
  
  
  Why self-hosted PaaS is booming in 2026
&lt;/h2&gt;

&lt;p&gt;The self-hosted PaaS surge is fundamentally a cost story. Cloud repatriation hit record interest in 2024–2026, with cost cited as the number-one driver in most surveys and high-profile exits making headlines. 37signals (makers of Basecamp and HEY) reported saving roughly &lt;strong&gt;$2 million per year&lt;/strong&gt; after leaving AWS, a figure its co-founder DHH has repeated publicly and that became a rallying cry for the movement. When a hobby project's $0 Heroku dyno turns into a $25–$100/month bill, a $5 VPS plus a free control panel looks irresistible.&lt;/p&gt;

&lt;p&gt;Developer mindshare followed the money. Coolify and Dokploy have become the two breakout repositories in this niche, and search demand for terms like "Coolify vs CapRover," "CapRover alternative," and "Dokploy vs Coolify 2026" has climbed accordingly. The pattern is consistent: teams want the Heroku/Vercel &lt;em&gt;developer experience&lt;/em&gt; — git push, automatic SSL, instant databases — without the per-seat, per-build, egress-metered Heroku/Vercel &lt;em&gt;bill&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It's worth keeping perspective: repatriation is selective, not wholesale. IDC data cited in the same roundups suggests only about 8% of organizations move &lt;em&gt;entire&lt;/em&gt; workloads off the public cloud. The dominant pattern is hybrid — keep some things managed, self-host the rest. That's exactly the gap these four tools fill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four contenders at a glance
&lt;/h2&gt;

&lt;p&gt;Here's the data-driven snapshot. Star counts come straight from each project's GitHub repository on June 22, 2026; pricing is from each vendor's official pricing page on the same date. Self-hosting every open-source option is free — you pay only for the server it runs on.&lt;/p&gt;

&lt;p&gt;Metric&lt;/p&gt;

&lt;p&gt;Coolify&lt;/p&gt;

&lt;p&gt;Dokploy&lt;/p&gt;

&lt;p&gt;CapRover&lt;/p&gt;

&lt;p&gt;Deploynix&lt;/p&gt;

&lt;p&gt;GitHub stars (Jun 2026)&lt;/p&gt;

&lt;p&gt;~57,300&lt;/p&gt;

&lt;p&gt;~35,000&lt;/p&gt;

&lt;p&gt;~15,100&lt;/p&gt;

&lt;p&gt;n/a (closed SaaS)&lt;/p&gt;

&lt;p&gt;First released&lt;/p&gt;

&lt;p&gt;2021&lt;/p&gt;

&lt;p&gt;2024&lt;/p&gt;

&lt;p&gt;2017&lt;/p&gt;

&lt;p&gt;2026&lt;/p&gt;

&lt;p&gt;Latest version&lt;/p&gt;

&lt;p&gt;v4.x (stable)&lt;/p&gt;

&lt;p&gt;v0.29.x (pre-1.0)&lt;/p&gt;

&lt;p&gt;v1.14.2&lt;/p&gt;

&lt;p&gt;Continuous SaaS&lt;/p&gt;

&lt;p&gt;License&lt;/p&gt;

&lt;p&gt;Apache-2.0&lt;/p&gt;

&lt;p&gt;Apache-2.0&lt;/p&gt;

&lt;p&gt;Apache-2.0&lt;/p&gt;

&lt;p&gt;Proprietary (SaaS)&lt;/p&gt;

&lt;p&gt;Built with&lt;/p&gt;

&lt;p&gt;PHP / Laravel&lt;/p&gt;

&lt;p&gt;TypeScript&lt;/p&gt;

&lt;p&gt;Node.js / TypeScript&lt;/p&gt;

&lt;p&gt;PHP / Laravel&lt;/p&gt;

&lt;p&gt;Container model&lt;/p&gt;

&lt;p&gt;Docker + Compose&lt;/p&gt;

&lt;p&gt;Docker Swarm + Traefik&lt;/p&gt;

&lt;p&gt;Docker Swarm + Nginx&lt;/p&gt;

&lt;p&gt;Native (no Docker required)&lt;/p&gt;

&lt;p&gt;Multi-server&lt;/p&gt;

&lt;p&gt;Yes (native)&lt;/p&gt;

&lt;p&gt;Yes (Swarm cluster)&lt;/p&gt;

&lt;p&gt;Yes (Swarm cluster)&lt;/p&gt;

&lt;p&gt;Yes (manage many VPS)&lt;/p&gt;

&lt;p&gt;Managed cloud option&lt;/p&gt;

&lt;p&gt;$5/mo (2 servers)&lt;/p&gt;

&lt;p&gt;$4.50/server (Hobby)&lt;/p&gt;

&lt;p&gt;None&lt;/p&gt;

&lt;p&gt;Free plan; Starter $12/mo&lt;/p&gt;

&lt;p&gt;Self-host cost&lt;/p&gt;

&lt;p&gt;Free&lt;/p&gt;

&lt;p&gt;Free&lt;/p&gt;

&lt;p&gt;Free&lt;/p&gt;

&lt;p&gt;n/a (control plane hosted)&lt;/p&gt;

&lt;h2&gt;
  
  
  Coolify: the feature-rich front-runner
&lt;/h2&gt;

&lt;p&gt;Coolify is the category leader by adoption and breadth, sitting at roughly &lt;strong&gt;57,300 GitHub stars&lt;/strong&gt; as of June 2026 (&lt;a href="https://github.com/coollabsio/coolify" rel="noopener noreferrer"&gt;coollabsio/coolify&lt;/a&gt;). Built in PHP/Laravel and licensed Apache-2.0, it positions itself as an open-source replacement for Heroku, Netlify, and Vercel combined — and largely delivers, with git-based deployments, automatic Let's Encrypt SSL, managed databases, and a library the project advertises as &lt;strong&gt;280+ one-click services&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Coolify does well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Breadth of features. Dockerfile and Nixpacks builds, Docker Compose, managed databases (PostgreSQL, MySQL, MariaDB, MongoDB, Redis), backups, monitoring, and native multi-server orchestration from a single dashboard.&lt;/li&gt;
&lt;li&gt;Genuinely free self-hosting. The full feature set is free forever when you host it yourself — no artificial limits, no "open core" lockout of essentials.&lt;/li&gt;
&lt;li&gt;Active, independent development. Founder Andras Bacsai went full-time on Coolify and has publicly declined VC funding, sustaining the project through GitHub Sponsors, Open Collective, and the optional Coolify Cloud. Development is moving toward a v5 focused on core scalability.&lt;/li&gt;
&lt;li&gt;Optional managed plane. Coolify Cloud starts at $5/month connecting up to two of your servers, with additional servers at +$3/month — useful if you want Coolify to run the control plane while your apps stay on your own machines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Coolify costs you
&lt;/h3&gt;

&lt;p&gt;Power has a price: Coolify's surface area is large, and a self-hosted control plane is still &lt;em&gt;your&lt;/em&gt; control plane. You patch it, you recover it when an upgrade misbehaves, and you own the blast radius if the single server hosting both Coolify and your apps goes down. Teams that want "set it and forget it" sometimes find Coolify's breadth is more than they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dokploy: the lightweight challenger
&lt;/h2&gt;

&lt;p&gt;Dokploy is the fastest-rising name in the space, climbing to about &lt;strong&gt;35,000 GitHub stars&lt;/strong&gt; (&lt;a href="https://github.com/Dokploy/dokploy" rel="noopener noreferrer"&gt;Dokploy/dokploy&lt;/a&gt;) within roughly two years of its 2024 launch by Mauricio Siu. Written in TypeScript and built on &lt;strong&gt;Docker Swarm with Traefik&lt;/strong&gt; for routing and load balancing, it markets itself as an open-source alternative to Vercel, Netlify, and Heroku with a deliberately clean, modern interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Dokploy does well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Native multi-node clustering. Because it's Swarm-native, scaling from one server to a cluster is a first-class concept, not an afterthought.&lt;/li&gt;
&lt;li&gt;Full Docker Compose support and multiple build methods (Nixpacks, Dockerfile, buildpacks), plus managed PostgreSQL, MySQL, MariaDB, MongoDB, and Redis with automated backups to external storage.&lt;/li&gt;
&lt;li&gt;Clean UX and templates. One-click templates for popular open-source apps and per-resource CPU/memory/network monitoring make it approachable.&lt;/li&gt;
&lt;li&gt;Flexible cloud pricing. Dokploy Cloud offers a Hobby plan at $4.50/month per server and a Startup plan from $15/month for three servers, with enterprise/self-hosted options above that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Dokploy costs you
&lt;/h3&gt;

&lt;p&gt;Dokploy is still on a &lt;strong&gt;0.x version line&lt;/strong&gt; (v0.29.x as of mid-2026). It's stable enough that many run it in production, but "pre-1.0" means APIs and behaviors can still shift, and its ecosystem of guides and third-party integrations is younger than Coolify's or CapRover's. In January 2026 the project also &lt;a href="https://dokploy.com/blog/we-are-updating-dokploys-open-source-license" rel="noopener noreferrer"&gt;updated its licensing&lt;/a&gt;, keeping the core Apache-2.0 while introducing a separate source-available license for future paid features — worth reading if license purity matters to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  CapRover: the battle-tested veteran
&lt;/h2&gt;

&lt;p&gt;CapRover is the elder statesman, in continuous development since 2017 and sitting at roughly &lt;strong&gt;15,100 GitHub stars&lt;/strong&gt; (&lt;a href="https://github.com/caprover/caprover" rel="noopener noreferrer"&gt;caprover/caprover&lt;/a&gt;). It's built on &lt;strong&gt;Docker Swarm with Nginx&lt;/strong&gt; as the reverse proxy, ships automatic Let's Encrypt SSL and NetData monitoring, and is famous for a one-line install and 100M+ Docker Hub pulls. Its latest release, v1.14.2, landed in May 2026 as a security hotfix — proof it's still maintained.&lt;/p&gt;

&lt;h3&gt;
  
  
  What CapRover does well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Maturity and stability. Eight-plus years in production make it a known quantity. Maintainer Kasra Bigdeli deliberately avoids niche features to keep the maintenance burden low and the platform dependable.&lt;/li&gt;
&lt;li&gt;Simplicity. A straightforward web GUI and CLI, one-click apps and databases, and a simple &lt;code&gt;captain-definition&lt;/code&gt; file for deployments. In-place upgrades are painless.&lt;/li&gt;
&lt;li&gt;Swarm clustering. You can add worker nodes through the UI for horizontal scaling (a Docker registry is required for multi-node setups).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where CapRover costs you
&lt;/h3&gt;

&lt;p&gt;CapRover lands last on modernity in nearly every 2026 comparison. The UI feels dated and is occasionally buggy; the release cadence is slow (a handful of releases per year); Docker Compose support is limited to a custom format rather than full Compose; and there's no preview-deployment or multi-environment workflow. It's also effectively a solo-maintained project, and there is &lt;strong&gt;no official managed cloud&lt;/strong&gt; — CapRover is self-host only. If you want cutting-edge features, CapRover will frustrate you; if you want something that just keeps working, that conservatism is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploynix: the Laravel-first managed option
&lt;/h2&gt;

&lt;p&gt;Deploynix is intentionally a different animal. Rather than an open-source panel you install and maintain, it's a &lt;strong&gt;managed SaaS control plane that provisions and operates native servers on cloud accounts you already own&lt;/strong&gt; — DigitalOcean, Hetzner, Vultr, Linode, AWS, or any custom Ubuntu box over SSH. There's no Docker layer between your code and the metal: it installs Nginx, PHP 8.1–8.4, MySQL/MariaDB/PostgreSQL, Redis/Valkey, Node, Supervisor, and Certbot directly, the way a senior Laravel ops engineer would. In that sense it's less a Coolify competitor and more a &lt;strong&gt;Laravel Forge alternative&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Deploynix does well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Zero infrastructure to babysit. The control plane is hosted and maintained for you. You never patch a panel or recover a crashed dashboard — you connect a provider and deploy.&lt;/li&gt;
&lt;li&gt;Native, not containerized. Apps run on native PHP-FPM and Nginx, which removes container overhead and matches how most production Laravel apps actually run. Provisioning a ready server takes about three minutes.&lt;/li&gt;
&lt;li&gt;True zero-downtime deploys. Each release lands in its own directory; once the build succeeds and health checks pass, a symlink switches atomically. If anything fails, the previous version stays live, and you can roll back instantly.&lt;/li&gt;
&lt;li&gt;Laravel-aware management. First-class handling of Laravel Horizon, queues, scheduled tasks, daemons, SSL, database management, and automated backups — with SSH keys and environment variables encrypted at rest.&lt;/li&gt;
&lt;li&gt;You own the infrastructure. Servers live in your cloud account and you pay the provider directly, so there's no markup on compute and no lock-in to someone else's hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where Deploynix costs you
&lt;/h3&gt;

&lt;p&gt;Deploynix is opinionated: it's built for Laravel and PHP, so polyglot teams deploying Go, Rust, or arbitrary Docker images are better served by Coolify or Dokploy. And unlike the open-source trio, the control plane itself is a paid product — there's a free plan, with the Starter tier at &lt;strong&gt;$12/month&lt;/strong&gt; (cloud-provider fees are separate, as they are for everyone). You're trading the "free but you run it" model for "paid but maintained for you."&lt;/p&gt;

&lt;h2&gt;
  
  
  The differences that actually matter
&lt;/h2&gt;

&lt;p&gt;Feature checklists blur together; three decisions don't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker panel vs native provisioning
&lt;/h3&gt;

&lt;p&gt;Coolify, Dokploy, and CapRover all containerize your apps — great for polyglot stacks and isolation, but it adds a layer to understand and debug. Deploynix provisions native services with no Docker requirement, which Laravel/PHP teams often prefer for performance and operational simplicity. If your stack is heterogeneous, containers win; if it's PHP-centric, native is leaner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-managed control plane vs managed SaaS
&lt;/h3&gt;

&lt;p&gt;This is the core fork in the road. With the open-source three, the control panel runs on your server — usually the &lt;em&gt;same&lt;/em&gt; server as your apps in small setups — so a bad panel upgrade or a host failure can take down both. With Deploynix, the control plane is SaaS: if it has an issue, your already-deployed apps keep serving traffic because they run on your independent VPS. You pay for that separation with a subscription.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing model
&lt;/h3&gt;

&lt;p&gt;"Free" open-source tools still cost you a server plus your time to run and secure the panel. Paid managed planes (Coolify Cloud at $5/mo, Dokploy Cloud from $4.50/server, Deploynix from $12/mo) trade dollars for that time. The honest comparison isn't free-vs-paid; it's &lt;strong&gt;"how much is an hour of your ops time worth?"&lt;/strong&gt; For a solo developer with one side project, free self-hosting wins easily. For a team shipping client work, a managed plane often pays for itself in a single avoided outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which self-hosted PaaS should you choose?
&lt;/h2&gt;

&lt;p&gt;Match the tool to the situation rather than chasing star counts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose Coolify if you want the most capable free, self-hosted panel for a mixed stack and don't mind maintaining it.&lt;/li&gt;
&lt;li&gt;Choose Dokploy if you want a modern, lightweight, Swarm-native panel and are comfortable on a pre-1.0 (but rapidly maturing) project.&lt;/li&gt;
&lt;li&gt;Choose CapRover if you value stability and simplicity over features and want a tool that has quietly worked for years.&lt;/li&gt;
&lt;li&gt;Choose Deploynix if you run Laravel or PHP, want native zero-downtime deploys on your own cloud account, and would rather pay a little than maintain a control panel at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many teams end up using two: an open-source panel for experiments and a managed plane for the apps that pay the bills. There's no wrong answer — only the wrong fit for your stack and your tolerance for ops work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Dokku, Kubernetes, and other alternatives?
&lt;/h2&gt;

&lt;p&gt;The four tools above dominate the conversation, but they aren't the only options — and knowing the neighbors clarifies the trade-offs. &lt;strong&gt;Dokku&lt;/strong&gt; is the original lightweight, single-server, git-push PaaS; it's powerful and free but CLI-first with no real web UI, so it appeals to terminal purists rather than teams who want a dashboard. &lt;strong&gt;Kubernetes&lt;/strong&gt; (via k3s, or managed control planes) is the heavyweight answer: maximal flexibility and scale, but operational complexity that's overkill for the vast majority of apps these PaaS tools target. And fully managed platforms like &lt;strong&gt;Render&lt;/strong&gt; and &lt;strong&gt;Railway&lt;/strong&gt; recreate the Heroku experience without any self-hosting — convenient, but they put you back on someone else's metered infrastructure, which is the exact bill most people are trying to escape.&lt;/p&gt;

&lt;p&gt;The honest framing: Coolify, Dokploy, and CapRover sit in the sweet spot between Dokku's bare-bones minimalism and Kubernetes' complexity, while Deploynix sits between the open-source panels and the fully managed platforms — managed control plane, self-owned servers. Where you land depends on how much control, and how much maintenance, you actually want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Coolify or Dokploy better in 2026?
&lt;/h3&gt;

&lt;p&gt;Coolify is more feature-complete and has a larger community (~57,300 vs ~35,000 GitHub stars in June 2026), making it the safer default. Dokploy is lighter, has a cleaner UI, and is Docker Swarm-native, which some prefer for clustering — but it's still pre-1.0. Choose Coolify for breadth, Dokploy for a lean, modern footprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is CapRover still worth using in 2026?
&lt;/h3&gt;

&lt;p&gt;Yes, if stability matters more than features. CapRover has been maintained since 2017, shipped a security release (v1.14.2) in May 2026, and remains rock-solid. Its trade-offs are a dated UI, slower release pace, and limited Docker Compose support compared with Coolify and Dokploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are these self-hosted PaaS tools really free?
&lt;/h3&gt;

&lt;p&gt;Coolify, Dokploy, and CapRover are all free to self-host under Apache-2.0 — you pay only for the server they run on. Each also offers (or, for CapRover, doesn't) an optional paid managed cloud. Deploynix is a paid managed service, with a free plan and a Starter tier at $12/month, where you still bring and pay for your own cloud servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is Deploynix different from Coolify, Dokploy, and CapRover?
&lt;/h3&gt;

&lt;p&gt;Deploynix is a managed SaaS control plane, not an open-source panel you install. It provisions native (non-Docker) servers on your own DigitalOcean, Hetzner, Vultr, Linode, or AWS account and is purpose-built for Laravel and PHP — closer to Laravel Forge than to the Docker-based trio. You get zero-downtime deploys and managed Horizon/queues/SSL without maintaining the control plane yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I move off Heroku or Vercel to these tools?
&lt;/h3&gt;

&lt;p&gt;Yes — that's exactly what they're built for. All four recreate the core Heroku/Vercel experience (git deploys, automatic SSL, instant databases) on infrastructure you control, typically at a fraction of the cost. Pick a Docker panel (Coolify/Dokploy/CapRover) for polyglot apps, or Deploynix if your workload is Laravel/PHP and you want it managed.&lt;/p&gt;

</description>
      <category>selfhostedpaas</category>
      <category>coolify</category>
      <category>dokploy</category>
      <category>caprover</category>
    </item>
    <item>
      <title>Laravel Telescope vs Pulse vs Nightwatch (2026): Which Should You Use?</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Thu, 18 Jun 2026 17:23:10 +0000</pubDate>
      <link>https://dev.to/deploynix/laravel-telescope-vs-pulse-vs-nightwatch-2026-which-should-you-use-3517</link>
      <guid>https://dev.to/deploynix/laravel-telescope-vs-pulse-vs-nightwatch-2026-which-should-you-use-3517</guid>
      <description>&lt;p&gt;Laravel now ships three first-party tools for looking inside your app — and they're easy to confuse. Telescope, Pulse, and Nightwatch all "monitor" something, but they solve different problems, run in different places, and only one of them costs money. Picking the wrong one means either flying blind in production or paying for observability you don't need yet.&lt;/p&gt;

&lt;p&gt;Here's the short version: &lt;strong&gt;they're complementary, not competitors.&lt;/strong&gt; Telescope is for local debugging, Pulse is a self-hosted production dashboard, and Nightwatch is hosted, full-fidelity production observability. Most serious apps end up running Telescope locally plus either Pulse or Nightwatch (or both) in production. This guide breaks down exactly what each does, what it costs, and how to choose.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Key takeaways- Telescope = local debugging. Self-hosted, free (MIT), stores every request in your app database. Not built for production. - Pulse = self-hosted production dashboard. Free (MIT), shows aggregated metrics (slow queries, jobs, exceptions, server load). - Nightwatch = hosted SaaS observability. Keeps every event with tracing, alerts, and an exception-to-issue workflow. Free tier (300k events/mo), then $20–$300+/mo. - They stack: debug locally with Telescope, watch production with Pulse and/or Nightwatch. - Nightwatch pricing changed once since its June 2025 launch — always confirm current numbers on the official pricing page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's the difference between Telescope, Pulse, and Nightwatch?
&lt;/h2&gt;

&lt;p&gt;The cleanest way to understand the three is on two axes: &lt;strong&gt;where they run&lt;/strong&gt; (local vs production) and &lt;strong&gt;what they record&lt;/strong&gt; (every individual event vs aggregated metrics).&lt;/p&gt;

&lt;p&gt;Where it shines&lt;/p&gt;

&lt;p&gt;Data model&lt;/p&gt;

&lt;p&gt;Hosting&lt;/p&gt;

&lt;p&gt;Cost&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telescope&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Local development&lt;/p&gt;

&lt;p&gt;Every event, full single-request detail&lt;/p&gt;

&lt;p&gt;Self-hosted (your app DB)&lt;/p&gt;

&lt;p&gt;Free (MIT)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pulse&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Production&lt;/p&gt;

&lt;p&gt;Pre-aggregated metrics ("buckets")&lt;/p&gt;

&lt;p&gt;Self-hosted (your DB)&lt;/p&gt;

&lt;p&gt;Free (MIT)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nightwatch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Production&lt;/p&gt;

&lt;p&gt;Every event, retained in the cloud&lt;/p&gt;

&lt;p&gt;Hosted SaaS&lt;/p&gt;

&lt;p&gt;Free tier, then paid&lt;/p&gt;

&lt;p&gt;Laravel's own framing says it best. From the Nightwatch announcement: &lt;em&gt;"Telescope helps you debug locally. Pulse shows you aggregated metrics. Nightwatch gives you the full story of what's happening in production"&lt;/em&gt; (&lt;a href="https://laravel.com/blog/announcing-laravel-nightwatch" rel="noopener noreferrer"&gt;Laravel Blog&lt;/a&gt;, 2025). That single sentence is the decision tree. The rest of this article fills in the detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Laravel Telescope and when should you use it?
&lt;/h2&gt;

&lt;p&gt;Telescope is the official debugging companion for your &lt;strong&gt;local&lt;/strong&gt; Laravel environment. It records every request, query, job, exception, mail, notification, cache operation, scheduled task, and dump — then lets you drill into any single one with full context. The docs describe it as &lt;em&gt;"a wonderful companion to your local Laravel development environment"&lt;/em&gt; (&lt;a href="https://laravel.com/docs/telescope" rel="noopener noreferrer"&gt;Laravel Docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Telescope stores its entries in your own application database, in a &lt;code&gt;telescope_entries&lt;/code&gt; table created by its migrations. That's exactly why it's a local tool: on a busy production app, that table fills up fast. The docs are blunt about the trade-offs — you should schedule &lt;code&gt;telescope:prune&lt;/code&gt; daily (it clears entries older than 24 hours by default), and the Nightwatch comparison page states plainly that Telescope is &lt;em&gt;"not recommended for production environments."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; run it in production behind an authorization gate, and some teams do for short-lived deep debugging. But its design intent is local. By default the dashboard is only reachable when &lt;code&gt;APP_ENV&lt;/code&gt; is &lt;code&gt;local&lt;/code&gt;; expose it elsewhere and you must add a gate, or the dashboard becomes publicly accessible.&lt;/p&gt;

&lt;p&gt;It is very much alive — the latest release is v5.20.0 from April 2026, and it's MIT-licensed and free. Telescope's role just narrowed once Pulse and Nightwatch arrived: it owns the local-debugging niche, and the other two cover production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Telescope when:&lt;/strong&gt; you're developing locally and need to see exactly what a single request did — every query, the bindings, the timing, the exception stack. It's the fastest way to answer "why did &lt;em&gt;this&lt;/em&gt; request behave that way?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Laravel Pulse and how is it different from Telescope?
&lt;/h2&gt;

&lt;p&gt;Pulse is a lightweight, self-hosted &lt;strong&gt;production&lt;/strong&gt; dashboard that shows aggregated performance and usage at a glance. Where Telescope keeps every individual event, Pulse rolls data into pre-computed buckets — averages, counts, maxes — so it stays cheap to run even under real traffic. The Pulse docs draw the line explicitly: &lt;em&gt;"For in-depth debugging of individual events, check out Laravel Telescope"&lt;/em&gt; (&lt;a href="https://laravel.com/docs/pulse" rel="noopener noreferrer"&gt;Laravel Docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Out of the box, Pulse gives you cards for server health (CPU, memory, storage via &lt;code&gt;pulse:check&lt;/code&gt;), top application usage, exceptions, queues, slow requests, slow jobs, slow queries, slow outgoing requests, and cache hit/miss rates. The default "slow" threshold is 1,000ms. The dashboard is a Livewire app served at &lt;code&gt;/pulse&lt;/code&gt;, and like Telescope it's &lt;code&gt;local&lt;/code&gt;-only by default — in production you add a &lt;code&gt;viewPulse&lt;/code&gt; gate to control access.&lt;/p&gt;

&lt;p&gt;The headline feature is that it &lt;em&gt;"drops into an existing application without requiring any additional infrastructure."&lt;/em&gt; For low and medium traffic, it just writes to your existing database. As you scale, Pulse offers three levers to keep overhead down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dedicated database connection (&lt;code&gt;PULSE_DB_CONNECTION&lt;/code&gt;) so monitoring writes don't touch your primary.&lt;/li&gt;
&lt;li&gt;Redis ingest (run &lt;code&gt;pulse:work&lt;/code&gt;) to buffer captures instead of writing synchronously.&lt;/li&gt;
&lt;li&gt;Sampling — e.g. a sample rate of &lt;code&gt;0.1&lt;/code&gt; records roughly 10% of events and scales the numbers up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pulse also fails silently if a capture errors, so a monitoring hiccup never takes down your app. It's MIT-licensed, free, and stable (latest release v1.7.4, June 2026). One requirement to note: Pulse's first-party storage needs &lt;strong&gt;MySQL, MariaDB, or PostgreSQL.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Pulse when:&lt;/strong&gt; you want production signal — what's slow, what's erroring, which users are hammering the app — without a SaaS bill, without sending your data off-site, and without new infrastructure. It answers "is my app healthy right now, and where are the bottlenecks?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Laravel Nightwatch and what does it cost?
&lt;/h2&gt;

&lt;p&gt;Nightwatch is Laravel's &lt;strong&gt;hosted&lt;/strong&gt; observability platform, purpose-built for production. Unlike Telescope and Pulse, it's a managed SaaS: you install a package and a token, and an agent running &lt;em&gt;out of process&lt;/em&gt; ships events to Nightwatch's cloud. Because the agent is separate from your app, the overhead is tiny — the docs cite &lt;em&gt;"typically less than 3ms per request"&lt;/em&gt; (&lt;a href="https://nightwatch.laravel.com/docs/guides/faqs" rel="noopener noreferrer"&gt;Nightwatch FAQ&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The big difference from Pulse is fidelity. Nightwatch keeps &lt;strong&gt;every individual event&lt;/strong&gt; — requests, outgoing requests, jobs, queries, mail, commands, cache operations, scheduled tasks, notifications, and exceptions — not just aggregates. On top of that raw stream it layers the things a team actually needs in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timelines and tracing for individual requests, queries, and jobs (middleware → controller → cache → query timing).&lt;/li&gt;
&lt;li&gt;An exception-to-issue workflow — exceptions become trackable issues with assignment, priority, and status.&lt;/li&gt;
&lt;li&gt;Smart alerts routed by issue type.&lt;/li&gt;
&lt;li&gt;Per-user impact so you can see who a problem actually affected.&lt;/li&gt;
&lt;li&gt;Team collaboration, which is a core selling point over the single-developer dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Data is stored in US, EU, or Australia regions, with a default retention of 90 days (the usable "lookback" window varies by plan). Nightwatch was announced at Laracon AU in November 2024 and went generally available on &lt;strong&gt;June 16, 2025&lt;/strong&gt; (&lt;a href="https://laravel-news.com/laravel-nightwatch" rel="noopener noreferrer"&gt;Laravel News&lt;/a&gt;; &lt;a href="https://laravel.com/blog/announcing-laravel-nightwatch" rel="noopener noreferrer"&gt;Laravel Blog&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Nightwatch pricing (as of June 2026)
&lt;/h3&gt;

&lt;p&gt;Plan&lt;/p&gt;

&lt;p&gt;Price/mo&lt;/p&gt;

&lt;p&gt;Events/mo included&lt;/p&gt;

&lt;p&gt;Lookback&lt;/p&gt;

&lt;p&gt;Overage per 100k&lt;/p&gt;

&lt;p&gt;Performance monitors&lt;/p&gt;

&lt;p&gt;Free&lt;/p&gt;

&lt;p&gt;$0&lt;/p&gt;

&lt;p&gt;300,000&lt;/p&gt;

&lt;p&gt;14 days&lt;/p&gt;

&lt;p&gt;$0.50&lt;/p&gt;

&lt;p&gt;1&lt;/p&gt;

&lt;p&gt;Pro&lt;/p&gt;

&lt;p&gt;$20&lt;/p&gt;

&lt;p&gt;7.5M&lt;/p&gt;

&lt;p&gt;30 days&lt;/p&gt;

&lt;p&gt;$0.35&lt;/p&gt;

&lt;p&gt;10&lt;/p&gt;

&lt;p&gt;Team&lt;/p&gt;

&lt;p&gt;$60&lt;/p&gt;

&lt;p&gt;30M&lt;/p&gt;

&lt;p&gt;60 days&lt;/p&gt;

&lt;p&gt;$0.35&lt;/p&gt;

&lt;p&gt;20&lt;/p&gt;

&lt;p&gt;Business&lt;/p&gt;

&lt;p&gt;$300&lt;/p&gt;

&lt;p&gt;180M&lt;/p&gt;

&lt;p&gt;90 days&lt;/p&gt;

&lt;p&gt;$0.20&lt;/p&gt;

&lt;p&gt;30&lt;/p&gt;

&lt;p&gt;Enterprise&lt;/p&gt;

&lt;p&gt;Custom&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;All plans include unlimited seats. Beyond your quota, you can opt in to additional event ingestion billed per 100k up to a limit you set, invoiced at the end of the cycle.&lt;/p&gt;

&lt;p&gt;One important caveat: these numbers have &lt;strong&gt;already changed once&lt;/strong&gt; since launch (the free tier was 200k events and Pro was 5M at GA; both went up). So treat the table above as a June 2026 snapshot and confirm the live figures on the &lt;a href="https://nightwatch.laravel.com/pricing" rel="noopener noreferrer"&gt;official pricing page&lt;/a&gt; before you budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Nightwatch when:&lt;/strong&gt; you're running real production traffic, you need to debug incidents after they happen (not just see that they happened), and a team needs to triage exceptions together with alerting. It answers "what exactly happened in production, to whom, and who's fixing it?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Telescope vs Pulse vs Nightwatch: which should you choose?
&lt;/h2&gt;

&lt;p&gt;Choose by where you are in the app's life and what question you're trying to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just developing locally? Telescope. Free, deep, instant.&lt;/li&gt;
&lt;li&gt;App is in production, you want free insight and to keep data in-house? Pulse. Self-hosted aggregates, no bill, no external egress.&lt;/li&gt;
&lt;li&gt;Production app where incidents cost money and a team responds to them? Nightwatch. Per-event tracing, alerts, and an issue workflow are worth paying for.&lt;/li&gt;
&lt;li&gt;All of the above? Run all three. That's the intended setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pulse and Nightwatch are the only two that genuinely overlap, both living in the production-monitoring space. The trade-off is straightforward: Pulse is free, self-hosted, and aggregated; Nightwatch is paid, hosted, and keeps every event with tracing and team features. Plenty of teams run Pulse for the always-on free dashboard &lt;em&gt;and&lt;/em&gt; Nightwatch for deep incident analysis — they answer different questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this fits a Deploynix deployment
&lt;/h2&gt;

&lt;p&gt;Both self-hosted options run on the server you deploy to — which makes them a natural fit when you ship a Laravel app with &lt;a href="https://deploynix.io" rel="noopener noreferrer"&gt;Deploynix&lt;/a&gt;. Telescope rides along in your repo for local work and stays out of production behind its environment gate. Pulse runs on your production server with zero extra infrastructure, writing to the MySQL, MariaDB, or PostgreSQL database your stack already includes; add the &lt;code&gt;pulse:check&lt;/code&gt; command to your scheduler for server metrics and a &lt;code&gt;viewPulse&lt;/code&gt; gate so only your team can reach &lt;code&gt;/pulse&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nightwatch needs nothing from your server beyond the package and token, since its agent ships data to the cloud. Whichever you choose, wire the setup (migrations, the &lt;code&gt;telescope:prune&lt;/code&gt; schedule, the &lt;code&gt;pulse:work&lt;/code&gt; worker if you use Redis ingest) into your deploy hooks so monitoring is provisioned the same way every release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I run Telescope, Pulse, and Nightwatch at the same time?
&lt;/h3&gt;

&lt;p&gt;Yes — they're designed to coexist, and Laravel's own documentation cross-references them. The common pattern is Telescope for local debugging, then Pulse and/or Nightwatch in production. Pulse and Nightwatch overlap, so many teams pick one as primary, but there's no technical conflict in running all three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Laravel Telescope safe to use in production?
&lt;/h3&gt;

&lt;p&gt;It's not recommended. Telescope stores every event in your application database, which grows quickly under real traffic, and its dashboard is only gated to &lt;code&gt;local&lt;/code&gt; by default. You can enable it in production behind an authorization gate for short-term debugging, but Pulse or Nightwatch are the tools built for ongoing production monitoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are Telescope and Pulse really free?
&lt;/h3&gt;

&lt;p&gt;Yes. Both are open-source under the MIT license and cost nothing to use — you host them yourself on your own infrastructure. Your only "cost" is the database storage and minimal overhead they add. Nightwatch is the paid option, though it offers a free tier of 300,000 events per month.&lt;/p&gt;

&lt;h3&gt;
  
  
  What counts as an "event" in Nightwatch billing?
&lt;/h3&gt;

&lt;p&gt;Nightwatch counts requests, outgoing requests, jobs, queries, mail, commands, cache operations, scheduled tasks, notifications, and exceptions as billable events. A single web request can generate several events (the request plus its queries and cache hits), so estimate your volume before choosing a plan, and use sampling to control it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Pulse or Nightwatch slow down my app?
&lt;/h3&gt;

&lt;p&gt;Both are built for low overhead. Pulse writes asynchronously, fails silently on capture errors, and supports Redis ingest plus sampling to stay light under load. Nightwatch runs its agent out of process and reports typically under 3ms of added latency per request. For high-traffic apps, sampling in either tool keeps the cost and overhead down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;There's no single winner here — the right answer depends on the question you're asking. &lt;strong&gt;Telescope&lt;/strong&gt; is your local microscope. &lt;strong&gt;Pulse&lt;/strong&gt; is your free, self-hosted production dashboard for spotting bottlenecks. &lt;strong&gt;Nightwatch&lt;/strong&gt; is your hosted, full-fidelity production observability platform for when incidents matter and a team responds to them. Start with Telescope while you build, add Pulse the day you go live, and reach for Nightwatch when production traffic and team workflows justify it.&lt;/p&gt;

&lt;p&gt;Whatever you run, the easiest place to operate it is on infrastructure you control. &lt;a href="https://deploynix.io" rel="noopener noreferrer"&gt;Deploy your Laravel app with Deploynix&lt;/a&gt; and wire Telescope, Pulse, or Nightwatch into your release process from day one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources (retrieved 2026-06-18): *&lt;a href="https://laravel.com/docs/telescope" rel="noopener noreferrer"&gt;Laravel Telescope docs&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href="https://laravel.com/docs/pulse" rel="noopener noreferrer"&gt;Laravel Pulse docs&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href="https://laravel.com/blog/announcing-laravel-nightwatch" rel="noopener noreferrer"&gt;Announcing Laravel Nightwatch — Laravel Blog&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href="https://nightwatch.laravel.com/pricing" rel="noopener noreferrer"&gt;Nightwatch pricing&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href="https://nightwatch.laravel.com/docs/guides/faqs" rel="noopener noreferrer"&gt;Nightwatch FAQs&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href="https://nightwatch.laravel.com/nightwatch-vs-pulse" rel="noopener noreferrer"&gt;Nightwatch vs Pulse&lt;/a&gt;&lt;/em&gt;, &lt;em&gt;&lt;a href="https://laravel-news.com/laravel-nightwatch" rel="noopener noreferrer"&gt;Laravel Nightwatch — Laravel News&lt;/a&gt;&lt;/em&gt;. Nightwatch pricing is a June 2026 snapshot and may change — verify on the official pricing page.*&lt;/p&gt;

</description>
      <category>telescope</category>
      <category>pulse</category>
      <category>nightwatch</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Running Artisan Commands Across Tenants: A Guide to tenants:run (stancl/tenancy)</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Wed, 17 Jun 2026 15:57:55 +0000</pubDate>
      <link>https://dev.to/deploynix/running-artisan-commands-across-tenants-a-guide-to-tenantsrun-stancltenancy-5d8h</link>
      <guid>https://dev.to/deploynix/running-artisan-commands-across-tenants-a-guide-to-tenantsrun-stancltenancy-5d8h</guid>
      <description>&lt;p&gt;Running Artisan Commands Across Tenants: A Guide to tenants:run (stancl/tenancy)&lt;/p&gt;

&lt;p&gt;If you run a multi-tenant Laravel app with &lt;a href="https://tenancyforlaravel.com/" rel="noopener noreferrer"&gt;stancl/tenancy&lt;/a&gt;, sooner or later you need to run an Artisan command for every tenant at once — migrate their databases, clear their caches, or regenerate a nightly report. The problem is that &lt;code&gt;php artisan migrate&lt;/code&gt; only touches your central database. Each tenant gets left behind.&lt;/p&gt;

&lt;p&gt;Plenty of developers go hunting for a &lt;code&gt;tenants:artisan&lt;/code&gt; command to solve this. It doesn't exist. The command you actually want is &lt;code&gt;tenants:run&lt;/code&gt;, and it wraps any registered Artisan command in each tenant's context. This guide walks through &lt;code&gt;tenants:run&lt;/code&gt;, the dedicated tenant-aware commands, the programmatic API, and the gotchas that bite once you're past a handful of tenants.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Key takeaways- The generic command is &lt;code&gt;tenants:run {commandname}&lt;/code&gt; — not &lt;code&gt;tenants:artisan&lt;/code&gt;. It runs any registered Artisan command inside each tenant's context. - Scope it with &lt;code&gt;--tenants=ID&lt;/code&gt; (repeat the flag for several). Omit it and the command hits every tenant. - Forward arguments and options with &lt;code&gt;--argument="key=value"&lt;/code&gt; and &lt;code&gt;--option="key=value"&lt;/code&gt;. - For migrations, seeds, and rollbacks, prefer the dedicated commands: &lt;code&gt;tenants:migrate&lt;/code&gt;, &lt;code&gt;tenants:seed&lt;/code&gt;, &lt;code&gt;tenants:rollback&lt;/code&gt;. - It runs tenants sequentially. Once you pass a few hundred, queue a job per tenant instead of looping in one process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What does tenants:run actually do?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;tenants:run&lt;/code&gt; command iterates over your tenants and, for each one, initializes tenancy, runs the command you named, then ends tenancy before moving on. Initializing tenancy is what swaps the database connection, cache prefix, and filesystem paths over to that tenant via the package's bootstrappers — so the wrapped command behaves exactly as it would inside a tenant request.&lt;/p&gt;

&lt;p&gt;Here's the signature straight from the package source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan tenants:run &lt;span class="o"&gt;{&lt;/span&gt;commandname&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;--argument&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;--option&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;commandname&lt;/code&gt; is any Artisan command that's already registered in your app. The simplest case takes no extra arguments at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clear the cache for every tenant&lt;/span&gt;
php artisan tenants:run cache:clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes this is the same loop you'd write by hand: initialize tenant, call &lt;code&gt;cache:clear&lt;/code&gt;, end tenant, repeat. The package just hides the boilerplate and the context switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I target specific tenants?
&lt;/h2&gt;

&lt;p&gt;Use the &lt;code&gt;--tenants&lt;/code&gt; option, repeating it once per tenant ID. Leave it off and the command runs for &lt;em&gt;all&lt;/em&gt; tenants — which is convenient for cache clears but genuinely dangerous for anything destructive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# One tenant&lt;/span&gt;
php artisan tenants:run cache:clear &lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acme

&lt;span class="c"&gt;# Several tenants (repeat the flag)&lt;/span&gt;
php artisan tenants:run cache:clear &lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acme &lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;globex

&lt;span class="c"&gt;# All tenants (the default when --tenants is omitted)&lt;/span&gt;
php artisan tenants:run cache:clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get into the habit of scoping with &lt;code&gt;--tenants&lt;/code&gt; while testing. A mistyped command name fails loudly for one tenant; a destructive command run against the default "all" can ruin your evening.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I pass arguments and options to the command?
&lt;/h2&gt;

&lt;p&gt;This is where &lt;code&gt;tenants:run&lt;/code&gt; trips people up. You can't just append flags to the wrapped command, because the parser would assign them to &lt;code&gt;tenants:run&lt;/code&gt; itself. Instead, you pass them through with &lt;code&gt;--argument&lt;/code&gt; and &lt;code&gt;--option&lt;/code&gt;, each as a &lt;code&gt;key=value&lt;/code&gt; pair.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan tenants:run email:send &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acme &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--argument&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"subject=Welcome"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--option&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"queue=1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a command you'd normally invoke as &lt;code&gt;php artisan email:send "Welcome" --queue&lt;/code&gt; becomes the form above. Repeat &lt;code&gt;--argument&lt;/code&gt; and &lt;code&gt;--option&lt;/code&gt; for each value you need to forward. It's verbose, but it keeps the wrapper's flags and the inner command's flags from colliding.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should I use the dedicated commands instead?
&lt;/h2&gt;

&lt;p&gt;stancl/tenancy ships purpose-built commands for the operations you run most. They handle the right defaults for you — tenant migrations, for example, look in &lt;code&gt;database/migrations/tenant&lt;/code&gt; automatically rather than your central migrations folder. Reach for these before &lt;code&gt;tenants:run&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Command&lt;/p&gt;

&lt;p&gt;What it does&lt;/p&gt;

&lt;p&gt;Key options&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tenants:migrate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Runs tenant migrations against each tenant database&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--tenants=*&lt;/code&gt;, &lt;code&gt;--path&lt;/code&gt;, &lt;code&gt;--force&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tenants:migrate-fresh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Wipes and re-migrates a tenant database (destructive)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--tenants=*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tenants:rollback&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Reverses the last migration batch per tenant&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--tenants=*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tenants:seed&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Seeds each tenant database&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--tenants=*&lt;/code&gt;, &lt;code&gt;--force&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tenants:list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lists all tenants with their IDs and domains&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tenants:run&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Runs any other registered Artisan command per tenant&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--tenants=*&lt;/code&gt;, &lt;code&gt;--argument=*&lt;/code&gt;, &lt;code&gt;--option=*&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Migrate every tenant database (use --force in production)&lt;/span&gt;
php artisan tenants:migrate &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Roll back the last batch for two tenants&lt;/span&gt;
php artisan tenants:rollback &lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acme &lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;globex

&lt;span class="c"&gt;# Wipe and re-migrate a single tenant (destructive!)&lt;/span&gt;
php artisan tenants:migrate-fresh &lt;span class="nt"&gt;--tenants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acme

&lt;span class="c"&gt;# Seed every tenant non-interactively&lt;/span&gt;
php artisan tenants:seed &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail worth burning into memory: these commands run non-interactively when invoked from a deploy hook or scheduler, so any command that normally asks "are you sure?" in production needs &lt;code&gt;--force&lt;/code&gt;. Without it, &lt;code&gt;tenants:migrate&lt;/code&gt; will hang or abort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I run tenant commands from my own code?
&lt;/h2&gt;

&lt;p&gt;Yes, and it's often cleaner than shelling out to Artisan. Every tenant exposes a &lt;code&gt;run()&lt;/code&gt; method that initializes its context, executes a closure, then restores the previous context. For batches, &lt;code&gt;tenancy()-&amp;gt;runForMultiple()&lt;/code&gt; does the same across a collection.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>multitenancy</category>
      <category>stancltenancy</category>
      <category>devops</category>
    </item>
    <item>
      <title>Valkey vs Redis: API Protocol, Data Format &amp; Compatibility — The Technical Deep-Dive</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Tue, 16 Jun 2026 20:22:15 +0000</pubDate>
      <link>https://dev.to/deploynix/valkey-vs-redis-api-protocol-data-format-compatibility-the-technical-deep-dive-4m2e</link>
      <guid>https://dev.to/deploynix/valkey-vs-redis-api-protocol-data-format-compatibility-the-technical-deep-dive-4m2e</guid>
      <description>&lt;p&gt;When Redis changed its license on March 20, 2024, it triggered one of the fastest open-source forks in recent memory. Eight days later, on March 28, 2024, the Linux Foundation announced &lt;strong&gt;Valkey&lt;/strong&gt; — a community fork built from the last BSD-licensed Redis release, version 7.2.4 (&lt;a href="https://valkey.io/topics/migration/" rel="noopener noreferrer"&gt;Valkey migration docs&lt;/a&gt;). Two years on, the practical question for engineers isn't political. It's technical: how compatible are these two systems really, at the wire, at the disk, and at the command level?&lt;/p&gt;

&lt;p&gt;This deep-dive answers that. We'll look at the RESP protocol both projects speak, the RDB and AOF on-disk formats they read and write, the exact point where their command sets and file formats begin to diverge, and what the performance gap actually looks like once you anchor it to real hardware.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Key TakeawaysValkey forked from Redis OSS 7.2.4 and speaks an identical RESP2/RESP3 wire protocol — existing Redis clients connect unchanged (Valkey protocol spec, 2025).RDB and AOF files are fully interchangeable at the Redis 7.2 baseline, but RDB files written by Redis CE 7.4+ will not load into Valkey — the central forward-compatibility risk (Valkey, 2025).Valkey 8.0 reached roughly 1.2M requests/sec on AWS hardware — a ~230% jump over the 7.2 baseline on the same instance (Valkey, Sept 2024).Redis 8 (May 2025) folded the former Stack modules into core under a tri-license; Valkey keeps equivalents as separate BSD modules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why did Valkey fork from Redis in the first place?
&lt;/h2&gt;

&lt;p&gt;In March 2024, Redis Ltd. relicensed Redis from the permissive BSD-3-Clause to a dual source-available model — RSALv2 and SSPLv1 — starting with the 7.4 release (&lt;a href="https://www.percona.com/blog/the-redis-license-has-changed-what-you-need-to-know/" rel="noopener noreferrer"&gt;Percona, March 20, 2024&lt;/a&gt;). Source-available is not open source: the SSPL in particular restricts offering the software as a managed service, which directly affected cloud providers and distributions.&lt;/p&gt;

&lt;p&gt;The response was immediate. Within a week, AWS, Google Cloud, Oracle, and others rallied behind a Linux Foundation fork of the last BSD commit. That fork became Valkey, forked from Redis OSS 7.2.4 (&lt;a href="https://valkey.io/topics/migration/" rel="noopener noreferrer"&gt;Valkey migration docs&lt;/a&gt;). Major Linux distributions that ship default packages — Fedora, Debian, Ubuntu — moved toward Valkey because they cannot ship source-available software in their main repositories.&lt;/p&gt;

&lt;p&gt;The story didn't end there. In November 2024, Redis creator Salvatore Sanfilippo (antirez) rejoined the company, and on May 1, 2025, Redis added AGPLv3 as a third license option starting with Redis 8 (&lt;a href="https://redis.io/blog/agplv3/" rel="noopener noreferrer"&gt;Redis blog, May 1, 2025&lt;/a&gt;). Redis is once again available under a true open-source license — but by then Valkey had its own momentum, governance, and release cadence.&lt;/p&gt;

&lt;p&gt;As of mid-2026, both projects are healthy and shipping. Valkey's current line is &lt;strong&gt;9.1.x&lt;/strong&gt; (9.1.0 landed May 2026) with an 8.1.x maintenance branch, while Redis is on its &lt;strong&gt;8.x&lt;/strong&gt; line (&lt;a href="http://endoflife.date" rel="noopener noreferrer"&gt;endoflife.date&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Milestone&lt;/p&gt;

&lt;p&gt;Date&lt;/p&gt;

&lt;p&gt;What changed&lt;/p&gt;

&lt;p&gt;Redis relicense&lt;/p&gt;

&lt;p&gt;Mar 20, 2024&lt;/p&gt;

&lt;p&gt;BSD-3 → RSALv2 + SSPLv1 (from 7.4)&lt;/p&gt;

&lt;p&gt;Valkey fork&lt;/p&gt;

&lt;p&gt;Mar 28, 2024&lt;/p&gt;

&lt;p&gt;Forked from Redis 7.2.4 under the Linux Foundation&lt;/p&gt;

&lt;p&gt;antirez returns&lt;/p&gt;

&lt;p&gt;Nov 2024&lt;/p&gt;

&lt;p&gt;Redis creator rejoins Redis Ltd.&lt;/p&gt;

&lt;p&gt;Redis 8 + AGPLv3&lt;/p&gt;

&lt;p&gt;May 1, 2025&lt;/p&gt;

&lt;p&gt;Tri-license: RSALv2 / SSPLv1 / AGPLv3&lt;/p&gt;

&lt;h2&gt;
  
  
  Are Valkey and Redis API-compatible at the protocol level?
&lt;/h2&gt;

&lt;p&gt;Yes — at the wire-protocol level they are identical. Both Valkey and Redis speak the REdis Serialization Protocol (RESP), supporting both RESP2 and the newer RESP3 introduced in Redis 6 (&lt;a href="https://valkey.io/topics/protocol/" rel="noopener noreferrer"&gt;Valkey protocol spec&lt;/a&gt;). Because Valkey began as a literal fork of the Redis 7.2.4 source tree, its protocol handler is the same code. A client has no way to tell, at the byte level, whether it is talking to one or the other during a standard handshake and command exchange.&lt;/p&gt;

&lt;p&gt;This is the single most important compatibility fact for most teams. Your existing client library — &lt;code&gt;redis-py&lt;/code&gt;, &lt;code&gt;ioredis&lt;/code&gt;, &lt;code&gt;Jedis&lt;/code&gt;, &lt;code&gt;Lettuce&lt;/code&gt;, &lt;code&gt;go-redis&lt;/code&gt;, &lt;code&gt;StackExchange.Redis&lt;/code&gt; — connects to a Valkey server with no code change. The protocol negotiation, pipelining, pub/sub framing, and cluster redirection messages (&lt;code&gt;MOVED&lt;/code&gt;, &lt;code&gt;ASK&lt;/code&gt;) all behave the same.&lt;/p&gt;

&lt;p&gt;Consider a raw RESP exchange. A &lt;code&gt;SET&lt;/code&gt; command followed by &lt;code&gt;GET&lt;/code&gt; looks like this on the wire, and the framing is byte-for-byte the same on both servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C: *3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
S: +OK\r\n

C: *2\r\n$3\r\nGET\r\n$3\r\nfoo\r\n
S: $3\r\nbar\r\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RESP3 adds typed replies — maps, sets, doubles, big numbers, and push messages for client-side caching. Both projects negotiate RESP3 through the &lt;code&gt;HELLO 3&lt;/code&gt; command identically. So whether you use &lt;code&gt;HELLO&lt;/code&gt; to upgrade the connection or stay on RESP2, the behavior is consistent across both servers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Because Valkey is a source-level fork of Redis 7.2.4, it inherits the exact RESP2/RESP3 implementation. No client library, proxy, or monitoring agent that speaks the Redis protocol needs modification to work against Valkey — the wire format is unchanged (Valkey, 2025).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where do the command sets actually diverge?
&lt;/h2&gt;

&lt;p&gt;Divergence is not at the protocol layer — it's at the command-set layer, and it grows with each release past the 7.2.4 fork point. Both projects keep adding commands independently, so command parity is a per-version question, not a yes/no answer. The protocol that carries the commands stays identical; the available verbs do not.&lt;/p&gt;

&lt;p&gt;On the Redis side, Redis 7.4 introduced Hash Field Expiration — the &lt;code&gt;HEXPIRE&lt;/code&gt;, &lt;code&gt;HPEXPIRE&lt;/code&gt;, &lt;code&gt;HEXPIREAT&lt;/code&gt;, and &lt;code&gt;HTTL&lt;/code&gt; family — which lets you set TTLs on individual hash fields rather than the whole key. Valkey added equivalent functionality later, with hash-field expiration landing in the Valkey 9.0 cycle. The behavior converges, but the timing and edge-case semantics can differ, so test against your target version.&lt;/p&gt;

&lt;p&gt;Valkey has shipped its own operational additions too. Valkey 8.1 introduced &lt;code&gt;SET ... IFEQ&lt;/code&gt;, a conditional update that only writes if the current value matches an expected one — useful for optimistic concurrency without a full Lua script (&lt;a href="https://valkey.io/blog/valkey-8-1-0-ga/" rel="noopener noreferrer"&gt;Valkey, April 2025&lt;/a&gt;). The same release added &lt;code&gt;COMMANDLOG&lt;/code&gt;, extending the older slowlog concept to track large requests and replies, not just slow ones.&lt;/p&gt;

&lt;p&gt;Capability&lt;/p&gt;

&lt;p&gt;Redis&lt;/p&gt;

&lt;p&gt;Valkey&lt;/p&gt;

&lt;p&gt;RESP2 / RESP3&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Yes (identical)&lt;/p&gt;

&lt;p&gt;Hash field expiration (HEXPIRE family)&lt;/p&gt;

&lt;p&gt;Since 7.4&lt;/p&gt;

&lt;p&gt;Since 9.0&lt;/p&gt;

&lt;p&gt;Conditional &lt;code&gt;SET ... IFEQ&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Not native&lt;/p&gt;

&lt;p&gt;Since 8.1&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COMMANDLOG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Since 8.1&lt;/p&gt;

&lt;p&gt;Vector sets&lt;/p&gt;

&lt;p&gt;Since 8 (antirez)&lt;/p&gt;

&lt;p&gt;Via valkey-search module&lt;/p&gt;

&lt;p&gt;The practical takeaway: if your application sticks to the core data-type commands that existed at 7.2.4 — strings, hashes, lists, sets, sorted sets, streams, pub/sub, transactions, scripting — you have near-total portability in both directions. The moment you adopt a command added after the fork, verify it exists and behaves the same on the other project at the specific version you target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are RDB and AOF data formats compatible between Valkey and Redis?
&lt;/h2&gt;

&lt;p&gt;At the Redis 7.2 baseline, completely. Valkey reads and writes RDB snapshot files and AOF (append-only file) logs that are compatible with Redis OSS 7.2 — the RDB version 11 format (&lt;a href="https://valkey.io/topics/migration/" rel="noopener noreferrer"&gt;Valkey migration docs&lt;/a&gt;). This is what makes migration so painless for teams still on Redis 7.2 or earlier: you stop Redis, point Valkey at the same &lt;code&gt;dump.rdb&lt;/code&gt; or AOF directory, and start it. The data loads natively.&lt;/p&gt;

&lt;p&gt;The same compatibility underpins replication-based migration. Because Valkey speaks the Redis replication protocol and understands the Redis 7.2 RDB stream, you can configure a Valkey instance as a replica of a Redis 7.2 primary, let it sync, then promote it — a near-zero-downtime cutover.&lt;/p&gt;

&lt;p&gt;But there is a hard edge, and it's the most important caveat in this entire comparison. &lt;strong&gt;RDB files produced by Redis CE 7.4 and later are not compatible with Valkey&lt;/strong&gt; (&lt;a href="https://valkey.io/topics/migration/" rel="noopener noreferrer"&gt;Valkey migration docs&lt;/a&gt;). After the 7.2.4 fork, Redis evolved its RDB format to support new in-core data types — and those newer RDB files will not load into a Valkey server. The formats have diverged.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RDB interoperability is anchored to the 7.2 fork point, not to "Redis" in general. Valkey loads RDB and AOF files from Redis OSS 7.2, but Redis CE 7.4+ RDB files will not load into Valkey because both projects extended the format independently after the split (Valkey, 2025).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two consequences follow. First, if you're on Redis 7.4 or 8, a file-level migration to Valkey is no longer a drop-in copy — you need a logical migration path (replication from a compatible version, or a tool that reads keys over the wire and rewrites them). Second, treat the reverse direction with caution: loading Valkey-written files into Redis is not officially documented as supported, so don't assume bidirectional file interop. Plan migrations as one-directional from a known-compatible baseline.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means for your migration plan
&lt;/h3&gt;

&lt;p&gt;If you're on Redis ≤ 7.2: file copy or replica-promotion both work cleanly. If you're on Redis 7.4+ or Redis 8: use live replication where the source version allows it, or a key-by-key migration tool that operates at the command level rather than the file level. Always validate with a representative dataset before cutover, and snapshot both sides so you can roll back.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Valkey 8.x performance compare to Redis?
&lt;/h2&gt;

&lt;p&gt;This is where the fork stopped being a copy and started pulling ahead on its own engineering. Valkey 8.0 delivered roughly &lt;strong&gt;1.2M requests per second&lt;/strong&gt; on AWS hardware — a separate published test measured a jump from about 360K to 1.19M RPS, a ~230% gain, on a c7g.16xlarge instance with 8 I/O threads (&lt;a href="https://valkey.io/blog/unlock-one-million-rps/" rel="noopener noreferrer"&gt;Valkey, Sept 2024&lt;/a&gt;). That number is real, but it is hardware-specific: it reflects a high-core-count ARM instance with multiple I/O threads and a particular payload. Don't quote it as a universal figure.&lt;/p&gt;

&lt;p&gt;Three architectural changes drove the Valkey 8.0 leap (&lt;a href="https://valkey.io/blog/valkey-8-0-0-rc1/" rel="noopener noreferrer"&gt;Valkey 8.0 RC1, Aug 2024&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asynchronous I/O multithreading — offloads socket reads, writes, and command parsing (including &lt;code&gt;epoll_wait&lt;/code&gt; handling) to I/O threads, freeing the main thread for command execution.&lt;/li&gt;
&lt;li&gt;Per-slot dictionaries — in cluster mode, each hash slot gets its own dictionary instead of one shared linked structure, cutting roughly 16 bytes per key-value pair and improving cache locality.&lt;/li&gt;
&lt;li&gt;Dual-channel replication — streams the RDB snapshot and the replication backlog in parallel over two channels, reducing full-sync time by up to 50%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Valkey 8.1 (April 2, 2025) pushed efficiency further: memory dropped by about 20 bytes per key-value pair without a TTL and 30 bytes with one, TLS connection-accept throughput rose roughly 300%, SET and GET over TLS improved ~10% and ~22%, and &lt;code&gt;BITCOUNT&lt;/code&gt; gained an AVX2-accelerated path measured at +514% (&lt;a href="https://valkey.io/blog/valkey-8-1-0-ga/" rel="noopener noreferrer"&gt;Valkey 8.1 GA&lt;/a&gt;). Independent benchmarking corroborates the throughput claims — Momento measured Valkey 8.1.1 at roughly 1M SET RPS on a c8g.2xlarge with sub-millisecond p99 latency.&lt;/p&gt;

&lt;p&gt;Metric (c7g.16xlarge, 8 I/O threads)&lt;/p&gt;

&lt;p&gt;Valkey 7.2 baseline&lt;/p&gt;

&lt;p&gt;Valkey 8.0&lt;/p&gt;

&lt;p&gt;Throughput (RPS)&lt;/p&gt;

&lt;p&gt;~360K&lt;/p&gt;

&lt;p&gt;~1.19M (+230%)&lt;/p&gt;

&lt;p&gt;Memory per KV (cluster)&lt;/p&gt;

&lt;p&gt;baseline&lt;/p&gt;

&lt;p&gt;−16 bytes&lt;/p&gt;

&lt;p&gt;Full-sync time&lt;/p&gt;

&lt;p&gt;baseline&lt;/p&gt;

&lt;p&gt;up to −50%&lt;/p&gt;

&lt;p&gt;Redis 8 is no slouch — it shipped its own performance work and the bundled query engine — but the multithreaded I/O path and per-slot memory model give Valkey a clear, independently verified edge on high-core-count hardware. For latency-sensitive, high-throughput workloads, that gap is worth benchmarking on your own instance types.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do the module and ecosystem stories differ?
&lt;/h2&gt;

&lt;p&gt;This is the sharpest architectural divergence. Redis 8 folded the former Redis Stack modules directly into the core server: native JSON, time series, five probabilistic structures (Bloom, Cuckoo, Count-Min Sketch, Top-K, t-digest), vector sets, and the Redis Query Engine (the former RediSearch) — all shipping in-core under the tri-license (&lt;a href="https://redis.io/blog/what-is-valkey/" rel="noopener noreferrer"&gt;Redis, 2025&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Valkey took the opposite path: it keeps the core lean and ships equivalent capabilities as &lt;strong&gt;separate, BSD-licensed modules&lt;/strong&gt; in the Valkey Bundle — &lt;code&gt;valkey-search&lt;/code&gt; (vector and secondary indexing), &lt;code&gt;valkey-json&lt;/code&gt;, &lt;code&gt;valkey-bloom&lt;/code&gt;, and &lt;code&gt;valkey-ldap&lt;/code&gt;, with modules reaching GA through the 8.1 and 9.0 cycles. One philosophy bundles everything into a single binary; the other keeps a minimal core you extend deliberately.&lt;/p&gt;

&lt;p&gt;For client compatibility, this distinction mostly doesn't matter — standard RESP clients work against both servers because the protocol is shared. Where it matters is module-specific clients and query syntax: if you depend on RediSearch query features or RedisJSON path semantics, validate that the corresponding Valkey module matches the behavior you rely on, since they're now independently maintained codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which platforms have adopted Valkey?
&lt;/h2&gt;

&lt;p&gt;Adoption moved fast, led by the cloud providers most affected by the license change. AWS added Valkey to both ElastiCache and MemoryDB in October 2024, with a zero-downtime in-place upgrade path from Redis 7.2 — and priced it below the Redis-compatible option (&lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/10/amazon-elasticache-valkey" rel="noopener noreferrer"&gt;AWS, Oct 2024&lt;/a&gt;). Google Cloud added Valkey to Memorystore, and Oracle ships it on OCI.&lt;/p&gt;

&lt;p&gt;The community numbers reflect that backing. By its first anniversary, Valkey had passed &lt;strong&gt;100 million+ Docker pulls&lt;/strong&gt; and drawn 150+ contributors from 50+ organizations including Google, Oracle, AWS, Ericsson, ByteDance, Aiven, and Percona (&lt;a href="https://aws.amazon.com/blogs/database/year-one-of-valkey-open-source-innovations-and-elasticache-version-8-1-for-valkey/" rel="noopener noreferrer"&gt;AWS, 2025&lt;/a&gt;). On the distribution side, Fedora, Debian, and Ubuntu moved their default in-memory data store package to Valkey, which means a large share of new Linux deployments now get Valkey by default rather than Redis.&lt;/p&gt;

&lt;p&gt;For teams running their own infrastructure, the deployment story for Valkey and Redis is effectively identical — same config file format, same ports, same operational tooling. If you're standing up either on managed servers, a platform that provisions and manages your data store removes most of the day-two operational burden regardless of which engine you pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you migrate from Redis to Valkey?
&lt;/h2&gt;

&lt;p&gt;The decision usually comes down to three factors: licensing, version, and feature dependency. If you need a permissive open-source license — for distribution, for compliance, or to avoid the source-available restrictions — Valkey (BSD-3) or Redis 8 under AGPLv3 both qualify, where the older RSALv2/SSPLv1-only releases do not.&lt;/p&gt;

&lt;p&gt;If you're on Redis 7.2 or earlier and want the performance gains, Valkey 8.x is a low-risk upgrade: the wire protocol is identical, your clients don't change, and the RDB/AOF files load natively. If you depend on the bundled Redis 8 modules with their exact query semantics, weigh whether the Valkey module equivalents cover your use cases before committing. And if you're already on Redis 7.4+, remember that file-level migration to Valkey no longer works — plan a logical or replication-based path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Valkey a drop-in replacement for Redis?
&lt;/h3&gt;

&lt;p&gt;For most workloads, yes. Valkey forked from Redis 7.2.4 and uses the identical RESP2/RESP3 protocol, so existing clients connect without code changes and RDB/AOF files from Redis 7.2 load natively (&lt;a href="https://valkey.io/topics/migration/" rel="noopener noreferrer"&gt;Valkey, 2025&lt;/a&gt;). The exceptions are commands added after the fork and the bundled Redis 8 modules, which you should validate per version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Valkey read Redis RDB files?
&lt;/h3&gt;

&lt;p&gt;Valkey reads RDB and AOF files compatible with Redis OSS 7.2 (RDB version 11). However, RDB files written by Redis CE 7.4 and later are not compatible with Valkey, because the on-disk format diverged after the 7.2.4 fork point (&lt;a href="https://valkey.io/topics/migration/" rel="noopener noreferrer"&gt;Valkey migration docs&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Valkey faster than Redis?
&lt;/h3&gt;

&lt;p&gt;On high-core-count hardware, Valkey 8.0 showed roughly a 230% throughput gain over the 7.2 baseline — up to ~1.2M RPS on a c7g.16xlarge with 8 I/O threads (&lt;a href="https://valkey.io/blog/unlock-one-million-rps/" rel="noopener noreferrer"&gt;Valkey, Sept 2024&lt;/a&gt;). The gains come from async I/O threading and per-slot dictionaries. Always benchmark on your own instance types, since the figures are hardware-specific.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need different client libraries for Valkey?
&lt;/h3&gt;

&lt;p&gt;No. Because the wire protocol is identical to Redis, standard Redis client libraries — &lt;code&gt;redis-py&lt;/code&gt;, &lt;code&gt;ioredis&lt;/code&gt;, &lt;code&gt;Jedis&lt;/code&gt;, &lt;code&gt;go-redis&lt;/code&gt;, and others — work against Valkey unchanged. Dedicated Valkey clients exist but are optional; the protocol compatibility is what matters (&lt;a href="https://valkey.io/topics/protocol/" rel="noopener noreferrer"&gt;Valkey protocol spec&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Redis still open source in 2026?
&lt;/h3&gt;

&lt;p&gt;Yes, again. Redis added AGPLv3 as a license option starting with Redis 8 in May 2025, making it open source under OSI-approved terms alongside the source-available RSALv2 and SSPLv1 options (&lt;a href="https://redis.io/blog/agplv3/" rel="noopener noreferrer"&gt;Redis, May 2025&lt;/a&gt;). Releases between 7.4 and 8 were source-available only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Valkey and Redis remain remarkably compatible where it counts most — the RESP wire protocol is byte-identical, and your client code, monitoring, and proxies carry over with zero changes. The real divergence lives at two layers: the command set, which grows apart with each release past 7.2.4, and the on-disk RDB format, which is interoperable only at the Redis 7.2 baseline and breaks for Redis 7.4+ files.&lt;/p&gt;

&lt;p&gt;For teams on Redis 7.2 or earlier, Valkey 8.x is a fast, low-risk upgrade with a genuine, independently verified performance edge. For teams on Redis 7.4+, migration is still very doable — it just requires a logical or replication-based path rather than a file copy. Either way, benchmark on your own hardware, pin to specific versions when you test command and module behavior, and snapshot both sides before you cut over.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: *&lt;a href="https://valkey.io/topics/migration/" rel="noopener noreferrer"&gt;Valkey — Migration from Redis&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://valkey.io/topics/protocol/" rel="noopener noreferrer"&gt;Valkey — Protocol spec&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://valkey.io/blog/valkey-8-0-0-rc1/" rel="noopener noreferrer"&gt;Valkey 8.0 RC1&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://valkey.io/blog/unlock-one-million-rps/" rel="noopener noreferrer"&gt;Valkey — 1M RPS&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://valkey.io/blog/valkey-8-1-0-ga/" rel="noopener noreferrer"&gt;Valkey 8.1 GA&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://redis.io/blog/agplv3/" rel="noopener noreferrer"&gt;Redis — AGPLv3&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://redis.io/blog/what-is-valkey/" rel="noopener noreferrer"&gt;Redis — What is Valkey&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://www.percona.com/blog/the-redis-license-has-changed-what-you-need-to-know/" rel="noopener noreferrer"&gt;Percona — Redis license change&lt;/a&gt;&lt;/em&gt; · &lt;em&gt;&lt;a href="https://aws.amazon.com/blogs/database/year-one-of-valkey-open-source-innovations-and-elasticache-version-8-1-for-valkey/" rel="noopener noreferrer"&gt;AWS — Year One of Valkey&lt;/a&gt;&lt;/em&gt;. Retrieved 2026-06-16.*&lt;/p&gt;

</description>
      <category>valkey</category>
      <category>redis</category>
      <category>respprotocol</category>
      <category>rdb</category>
    </item>
    <item>
      <title>How Much Traffic Can a $5 Server Handle? Load Testing Laravel on Deploynix</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sun, 26 Apr 2026 09:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/how-much-traffic-can-a-5-server-handle-load-testing-laravel-on-deploynix-1co5</link>
      <guid>https://dev.to/deploynix/how-much-traffic-can-a-5-server-handle-load-testing-laravel-on-deploynix-1co5</guid>
      <description>&lt;p&gt;Every developer launching a new project asks the same question: how much server do I actually need? The cloud providers offer servers starting at $4-6/month, but their marketing pages show theoretical bandwidth limits and CPU benchmarks that tell you nothing about how your Laravel application will perform under real-world load.&lt;/p&gt;

&lt;p&gt;So let's find out. In this post, we'll provision a $5 server on Deploynix, deploy a representative Laravel application, and progressively load test it until it breaks. Along the way, we'll apply optimizations and measure their impact. By the end, you'll know exactly what a budget server can handle — and when it's time to scale up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test Environment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Server
&lt;/h3&gt;

&lt;p&gt;We're using a typical $5/month cloud server — the kind you'd get from Hetzner, DigitalOcean, or Vultr at this price point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU: 1 shared vCPU&lt;/li&gt;
&lt;li&gt;RAM: 1 GB&lt;/li&gt;
&lt;li&gt;Disk: 25 GB SSD&lt;/li&gt;
&lt;li&gt;Network: 1 Gbps&lt;/li&gt;
&lt;li&gt;OS: Ubuntu 24.04&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the smallest server most providers offer. Deploynix provisions it as an App server — running Nginx, PHP 8.4 with FPM, MySQL, Valkey, and Supervisor all on the same machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Application
&lt;/h3&gt;

&lt;p&gt;Testing against a "Hello World" endpoint is meaningless. Real Laravel applications hit the database, render Blade views, check authentication, and process middleware. Our test application represents a typical content site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication middleware on all routes&lt;/li&gt;
&lt;li&gt;A dashboard page that queries 5 Eloquent models with relationships&lt;/li&gt;
&lt;li&gt;A listing page with pagination (20 items per page, eager-loaded relationships)&lt;/li&gt;
&lt;li&gt;An API endpoint returning JSON through an Eloquent Resource&lt;/li&gt;
&lt;li&gt;Active sessions stored in Valkey&lt;/li&gt;
&lt;li&gt;Config, route, and view caches enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives us a realistic baseline that reflects what most Laravel applications actually do.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Load Testing Tool
&lt;/h3&gt;

&lt;p&gt;We'll use &lt;code&gt;k6&lt;/code&gt;, a modern load testing tool that's developer-friendly and doesn't consume excessive resources on the machine running the tests. A basic k6 script looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Ramp to 10 users&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Stay at 10&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Ramp to 50&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Stay at 50&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// Ramp to 100&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// Stay at 100&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;    &lt;span class="c1"&gt;// Ramp down&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-app.deploynix.cloud/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;response time &amp;lt; 500ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run tests from a separate machine — never from the server you're testing. Loading testing from the same server contaminates results because the test tool competes with the application for resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Baseline Test (No Optimization)
&lt;/h2&gt;

&lt;p&gt;First, let's deploy the application with default settings — no caching commands, no optimization. This represents what happens when a developer pushes code and forgets to run the optimization commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  10 Concurrent Users
&lt;/h3&gt;

&lt;p&gt;At 10 concurrent users with a 1-second think time (each user makes a request, waits 1 second, repeats):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~9.5&lt;/li&gt;
&lt;li&gt;Median response time: 95ms&lt;/li&gt;
&lt;li&gt;P95 response time: 180ms&lt;/li&gt;
&lt;li&gt;P99 response time: 310ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~25%&lt;/li&gt;
&lt;li&gt;Memory usage: ~680MB (of 1GB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this level, the server handles everything comfortably. Response times are good, and there's plenty of headroom.&lt;/p&gt;

&lt;h3&gt;
  
  
  50 Concurrent Users
&lt;/h3&gt;

&lt;p&gt;Ramping to 50 concurrent users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~42&lt;/li&gt;
&lt;li&gt;Median response time: 320ms&lt;/li&gt;
&lt;li&gt;P95 response time: 890ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,400ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~78%&lt;/li&gt;
&lt;li&gt;Memory usage: ~720MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things are getting warm. The P95 response time approaching 1 second means one in twenty requests feels sluggish. CPU is climbing. Memory is tight — MySQL and Valkey are competing with PHP workers for that 1GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  100 Concurrent Users
&lt;/h3&gt;

&lt;p&gt;Pushing to 100:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~48 (barely increased from 50 users)&lt;/li&gt;
&lt;li&gt;Median response time: 1,800ms&lt;/li&gt;
&lt;li&gt;P95 response time: 4,200ms&lt;/li&gt;
&lt;li&gt;P99 response time: 8,500ms&lt;/li&gt;
&lt;li&gt;Error rate: 3.2% (timeouts and 502 errors)&lt;/li&gt;
&lt;li&gt;CPU usage: 98%&lt;/li&gt;
&lt;li&gt;Memory usage: ~950MB (OOM killer territory)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server hit its wall. Request throughput plateaued at ~48 req/s while response times exploded. Some requests are timing out entirely. The CPU is saturated, and memory is dangerously close to triggering the OOM killer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseline breaking point: ~50 concurrent users, ~42 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Apply Laravel Optimizations
&lt;/h2&gt;

&lt;p&gt;Now let's apply the standard production optimizations and retest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also ensure &lt;code&gt;APP_DEBUG=false&lt;/code&gt; and the OPcache extension is enabled (Deploynix enables this by default).&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 50 Concurrent Users (Optimized)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~48&lt;/li&gt;
&lt;li&gt;Median response time: 210ms&lt;/li&gt;
&lt;li&gt;P95 response time: 450ms&lt;/li&gt;
&lt;li&gt;P99 response time: 680ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~60%&lt;/li&gt;
&lt;li&gt;Memory usage: ~650MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A significant improvement. Caching configuration and routes eliminated the overhead of parsing those files on every request. Response times nearly halved, and CPU dropped from 78% to 60%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 100 Concurrent Users (Optimized)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~82&lt;/li&gt;
&lt;li&gt;Median response time: 480ms&lt;/li&gt;
&lt;li&gt;P95 response time: 1,100ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,800ms&lt;/li&gt;
&lt;li&gt;Error rate: 0.5%&lt;/li&gt;
&lt;li&gt;CPU usage: 92%&lt;/li&gt;
&lt;li&gt;Memory usage: ~780MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We nearly doubled the throughput before hitting the wall. The server can now handle 100 concurrent users with acceptable (though not great) response times. The few errors are sporadic timeouts during CPU spikes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimized breaking point: ~100 concurrent users, ~82 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: Switch to FrankenPHP (Octane)
&lt;/h2&gt;

&lt;p&gt;FrankenPHP keeps the Laravel application bootstrapped in memory between requests. Instead of loading the framework, service providers, and configuration on every request, the application boots once and handles subsequent requests from memory.&lt;/p&gt;

&lt;p&gt;Deploy with FrankenPHP through Deploynix's Octane driver selection, then retest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 100 Concurrent Users (FrankenPHP)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~145&lt;/li&gt;
&lt;li&gt;Median response time: 180ms&lt;/li&gt;
&lt;li&gt;P95 response time: 420ms&lt;/li&gt;
&lt;li&gt;P99 response time: 690ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~70%&lt;/li&gt;
&lt;li&gt;Memory usage: ~520MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a dramatic improvement. FrankenPHP eliminated the per-request framework bootstrap overhead, nearly doubling throughput again. Memory usage actually dropped because the application is loaded once instead of per-request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 200 Concurrent Users (FrankenPHP)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~180&lt;/li&gt;
&lt;li&gt;Median response time: 450ms&lt;/li&gt;
&lt;li&gt;P95 response time: 1,100ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,900ms&lt;/li&gt;
&lt;li&gt;Error rate: 0.8%&lt;/li&gt;
&lt;li&gt;CPU usage: 94%&lt;/li&gt;
&lt;li&gt;Memory usage: ~600MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 200 concurrent users, the single vCPU is the bottleneck. The application is still responsive but the CPU can't keep up. Throughput has started to plateau.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FrankenPHP breaking point: ~200 concurrent users, ~180 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 4: Database Query Optimization
&lt;/h2&gt;

&lt;p&gt;Our test application had a few unoptimized queries — missing eager loading on the listing page and no indexes on commonly filtered columns. After fixing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: N+1 problem&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// In view: $post-&amp;gt;author-&amp;gt;name (triggers N+1)&lt;/span&gt;

&lt;span class="c1"&gt;// After: Eager loaded&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And adding a composite index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'published_at'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Results at 200 Concurrent Users (FrankenPHP + Query Optimization)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~210&lt;/li&gt;
&lt;li&gt;Median response time: 380ms&lt;/li&gt;
&lt;li&gt;P95 response time: 820ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,300ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: 88%&lt;/li&gt;
&lt;li&gt;Memory usage: ~550MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Query optimization reduced database CPU time, which freed up CPU cycles for handling more requests. The errors at 200 users disappeared entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pushing to 300 Concurrent Users
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~225&lt;/li&gt;
&lt;li&gt;Median response time: 850ms&lt;/li&gt;
&lt;li&gt;P95 response time: 2,100ms&lt;/li&gt;
&lt;li&gt;P99 response time: 3,400ms&lt;/li&gt;
&lt;li&gt;Error rate: 1.5%&lt;/li&gt;
&lt;li&gt;CPU usage: 98%&lt;/li&gt;
&lt;li&gt;Memory usage: ~620MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CPU is fully saturated. We've found the ceiling for this $5 server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fully optimized breaking point: ~250 concurrent users, ~220 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does This Mean in Real Traffic?
&lt;/h2&gt;

&lt;p&gt;Concurrent users is a load testing metric, not a real-world traffic metric. Real users don't send a request every second — they read pages, fill out forms, and navigate at human speed. The ratio depends on your application, but a common rule of thumb:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 concurrent user in load testing ≈ 10-30 real active users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So our fully optimized $5 server handling 250 concurrent users translates to roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2,500 - 7,500 active users browsing simultaneously&lt;/li&gt;
&lt;li&gt;~220 requests/second sustained throughput&lt;/li&gt;
&lt;li&gt;~19 million requests/day at sustained peak&lt;/li&gt;
&lt;li&gt;~570 million requests/month at sustained peak&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most applications don't sustain peak traffic 24/7. If your peak is 4 hours/day, a $5 server can handle an application with 50,000-100,000 daily active users comfortably.&lt;/p&gt;

&lt;p&gt;That's a $5/month server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization Impact Summary
&lt;/h2&gt;

&lt;p&gt;Configuration&lt;/p&gt;

&lt;p&gt;Max RPS&lt;/p&gt;

&lt;p&gt;P95 at 50 Users&lt;/p&gt;

&lt;p&gt;Breaking Point&lt;/p&gt;

&lt;p&gt;Default (no caching)&lt;/p&gt;

&lt;p&gt;~42&lt;/p&gt;

&lt;p&gt;890ms&lt;/p&gt;

&lt;p&gt;~50 concurrent&lt;/p&gt;

&lt;p&gt;Laravel cache commands&lt;/p&gt;

&lt;p&gt;~82&lt;/p&gt;

&lt;p&gt;450ms&lt;/p&gt;

&lt;p&gt;~100 concurrent&lt;/p&gt;

&lt;p&gt;FrankenPHP (Octane)&lt;/p&gt;

&lt;p&gt;~180&lt;/p&gt;

&lt;p&gt;420ms&lt;/p&gt;

&lt;p&gt;~200 concurrent&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;~220&lt;/p&gt;

&lt;p&gt;320ms&lt;/p&gt;

&lt;p&gt;~250 concurrent&lt;/p&gt;

&lt;p&gt;Each optimization roughly doubled throughput. Combined, we went from 42 to 220 requests/second — a 5x improvement without spending an extra dollar on infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Scale Up
&lt;/h2&gt;

&lt;p&gt;Watch these signals on your Deploynix monitoring dashboard:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale up the server (vertical scaling) when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU consistently above 80% during normal traffic (not just spikes)&lt;/li&gt;
&lt;li&gt;Memory usage leaves less than 100MB free&lt;/li&gt;
&lt;li&gt;P95 response times exceed your acceptable threshold (usually 1-2 seconds)&lt;/li&gt;
&lt;li&gt;The OOM killer has been triggered (check &lt;code&gt;dmesg&lt;/code&gt; for killed processes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scale out to multiple servers (horizontal scaling) when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You've maxed out the largest single server your budget allows&lt;/li&gt;
&lt;li&gt;You need redundancy — a single server is a single point of failure&lt;/li&gt;
&lt;li&gt;Queue processing competes with web requests for CPU time&lt;/li&gt;
&lt;li&gt;Database operations need dedicated resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The typical scaling path on Deploynix:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with a single App server ($5-12/month)&lt;/li&gt;
&lt;li&gt;Upgrade to a larger server when CPU/memory is consistently tight ($24-48/month)&lt;/li&gt;
&lt;li&gt;Separate the database to its own server when query performance suffers&lt;/li&gt;
&lt;li&gt;Add a dedicated worker server when queues can't keep up&lt;/li&gt;
&lt;li&gt;Add a load balancer with multiple web servers when a single web server can't handle peak traffic&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cost-Effective Optimization Checklist
&lt;/h2&gt;

&lt;p&gt;Before spending money on bigger servers, make sure you've applied these free optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;APP_DEBUG=false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Run &lt;code&gt;config:cache&lt;/code&gt;, &lt;code&gt;route:cache&lt;/code&gt;, &lt;code&gt;view:cache&lt;/code&gt;, &lt;code&gt;event:cache&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] OPcache enabled with appropriate settings&lt;/li&gt;
&lt;li&gt;[ ] FrankenPHP, Swoole, or RoadRunner via Octane&lt;/li&gt;
&lt;li&gt;[ ] Eager loading on all relationship accesses (no N+1 queries)&lt;/li&gt;
&lt;li&gt;[ ] Database indexes on filtered and sorted columns&lt;/li&gt;
&lt;li&gt;[ ] Response caching for pages that don't change per-user&lt;/li&gt;
&lt;li&gt;[ ] Asset optimization (&lt;code&gt;npm run build&lt;/code&gt;, not &lt;code&gt;npm run dev&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Queue heavy operations instead of processing inline&lt;/li&gt;
&lt;li&gt;[ ] Valkey for sessions, cache, and queues (not file/database drivers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is free and most take minutes to implement. Collectively, they can improve performance by 5-10x.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A $5 server can handle far more traffic than most developers expect. With Laravel's built-in optimization commands and an Octane driver like FrankenPHP, a single shared vCPU server sustains over 200 requests per second — enough for thousands of concurrent users.&lt;/p&gt;

&lt;p&gt;The key insight is that optimization should come before scaling. A $5 server with FrankenPHP and proper caching outperforms a $48 server running unoptimized PHP-FPM. Only when you've exhausted the free optimizations should you reach for the scaling lever.&lt;/p&gt;

&lt;p&gt;Deploynix makes both paths easy. Optimize your application and deploy it to a small server. Monitor the metrics. When the dashboard tells you it's time, scale up with a click. No premature infrastructure spending, no guessing — just data-driven decisions about when your application needs more resources.&lt;/p&gt;

&lt;p&gt;Start small. Optimize first. Scale when the numbers tell you to.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>loadtesting</category>
      <category>performance</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>The Definitive Guide to Laravel Deployment in 2026</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 21:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/the-definitive-guide-to-laravel-deployment-in-2026-307o</link>
      <guid>https://dev.to/deploynix/the-definitive-guide-to-laravel-deployment-in-2026-307o</guid>
      <description>&lt;p&gt;Deploying a Laravel application in 2026 looks nothing like it did five years ago. PHP-FPM is no longer the only game in town — FrankenPHP, Swoole, and RoadRunner have changed what's possible for performance. Server management platforms have matured to the point where a solo developer can run production infrastructure that would have required a DevOps team. And the Laravel ecosystem itself has introduced tools for monitoring, testing, and scaling that make the deployment story more complete than ever.&lt;/p&gt;

&lt;p&gt;This guide covers the full journey: from your local development environment to a production deployment that's monitored, backed up, and ready to scale. Whether you're deploying your first Laravel application or rearchitecting an existing one, this is your comprehensive reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Preparing Your Application for Production
&lt;/h2&gt;

&lt;p&gt;Before you touch a server, your application needs to be production-ready. This means more than "it works on my machine."&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Configuration
&lt;/h3&gt;

&lt;p&gt;Laravel uses &lt;code&gt;.env&lt;/code&gt; files to separate configuration from code. Your production environment needs different values from development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com

LOG_CHANNEL=stack
LOG_LEVEL=warning

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=your_app
DB_USERNAME=your_user
DB_PASSWORD=strong_random_password

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical settings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;APP_DEBUG=false&lt;/code&gt; prevents stack traces from leaking to users. Leaving this &lt;code&gt;true&lt;/code&gt; in production is a security vulnerability.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LOG_LEVEL=warning&lt;/code&gt; prevents your log files from filling the disk with debug information.&lt;/li&gt;
&lt;li&gt;Use Redis (or Valkey, which is Redis-compatible) for cache, sessions, and queues in production. The file and database drivers don't scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Optimizing for Production
&lt;/h3&gt;

&lt;p&gt;Laravel provides several Artisan commands that optimize performance by caching configuration, routes, and views:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands serialize your configuration, routes, views, and events into cached files that load faster than parsing the original sources on every request. Run them as part of your deployment process — never manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asset Compilation
&lt;/h3&gt;

&lt;p&gt;Build your frontend assets for production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs Vite in production mode, which minifies JavaScript and CSS, tree-shakes unused code, and generates versioned filenames for cache busting. Never run &lt;code&gt;npm run dev&lt;/code&gt; on a production server — development mode includes source maps and unminified code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Choosing Your Server Infrastructure
&lt;/h2&gt;

&lt;p&gt;Your infrastructure choices depend on your application's needs, traffic expectations, and budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Providers
&lt;/h3&gt;

&lt;p&gt;Deploynix supports provisioning servers on DigitalOcean, Vultr, Hetzner, Linode, AWS, and custom providers. Each has trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hetzner offers the best price-to-performance ratio for CPU and RAM. Ideal for budget-conscious deployments.&lt;/li&gt;
&lt;li&gt;DigitalOcean provides a polished experience with predictable pricing. Strong ecosystem of add-ons.&lt;/li&gt;
&lt;li&gt;Vultr offers competitive pricing with good global coverage.&lt;/li&gt;
&lt;li&gt;Linode (now Akamai) provides solid performance with straightforward pricing.&lt;/li&gt;
&lt;li&gt;AWS gives maximum flexibility and the broadest service catalog, but with higher complexity and cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most Laravel applications starting out, a $5-12/month server on Hetzner or DigitalOcean is more than sufficient. You can always scale up later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Types
&lt;/h3&gt;

&lt;p&gt;A single App server handles everything for most applications: web serving, application processing, database, cache, and queue workers. As you grow, you split these responsibilities across specialized servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App Server:&lt;/strong&gt; Runs your Laravel application with Nginx, PHP, and queue workers. This is the all-in-one starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web Server:&lt;/strong&gt; Handles HTTP requests and serves static files. In a scaled architecture, multiple web servers sit behind a load balancer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database Server:&lt;/strong&gt; Dedicated MySQL, MariaDB, or PostgreSQL instance. Isolating the database gives it dedicated CPU and RAM, improving query performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Server:&lt;/strong&gt; Dedicated Valkey (Redis-compatible) instance for caching, sessions, and queues. Separating cache from the application server prevents cache eviction during memory pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker Server:&lt;/strong&gt; Runs queue workers without competing with web requests for resources. Essential when you process heavy background jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load Balancer:&lt;/strong&gt; Distributes traffic across multiple web servers. Deploynix supports round robin, least connections, and IP hash load balancing methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing a PHP Runtime
&lt;/h3&gt;

&lt;p&gt;Traditional PHP-FPM remains reliable and well-understood, but modern runtimes offer significant performance improvements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FrankenPHP&lt;/strong&gt; is a modern PHP application server built on Caddy. It supports worker mode (keeping your application in memory between requests), HTTP/3, and Early Hints. It's becoming the default recommendation for new Laravel deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swoole&lt;/strong&gt; keeps your application bootstrapped in memory, eliminating the per-request overhead of loading the framework. It provides dramatic performance improvements but requires careful attention to memory leaks and static state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RoadRunner&lt;/strong&gt; is a Go-based application server that communicates with PHP workers over a binary protocol. It offers performance between FPM and Swoole with simpler debugging.&lt;/p&gt;

&lt;p&gt;Deploynix supports deploying with any of these Octane drivers. For most applications, FrankenPHP provides the best balance of performance and developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Provisioning and Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Provisioning with Deploynix
&lt;/h3&gt;

&lt;p&gt;Deploynix provisions servers by connecting to your cloud provider's API. You select a provider, region, server size, and type — Deploynix handles the rest: installing the OS, configuring the web server, setting up PHP, installing the database, configuring the firewall, and setting up SSL.&lt;/p&gt;

&lt;p&gt;The provisioning process installs everything your Laravel application needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx (configured for your chosen Octane driver or PHP-FPM)&lt;/li&gt;
&lt;li&gt;PHP 8.4 with essential extensions&lt;/li&gt;
&lt;li&gt;MySQL, MariaDB, or PostgreSQL&lt;/li&gt;
&lt;li&gt;Valkey for caching and queues&lt;/li&gt;
&lt;li&gt;Composer&lt;/li&gt;
&lt;li&gt;Node.js and npm for asset compilation&lt;/li&gt;
&lt;li&gt;Supervisor for queue workers and daemons&lt;/li&gt;
&lt;li&gt;UFW firewall with sensible defaults&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Connecting Your Git Repository
&lt;/h3&gt;

&lt;p&gt;Deploynix integrates with GitHub, GitLab, Bitbucket, and custom Git providers. Connect your repository, select a branch, and Deploynix configures the deployment pipeline.&lt;/p&gt;

&lt;p&gt;Your deployment workflow becomes: push to your branch, trigger a deploy (manually or automatically), and Deploynix handles the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSL Certificates
&lt;/h3&gt;

&lt;p&gt;Every production application needs HTTPS. Deploynix provisions SSL certificates automatically through Let's Encrypt when you add a domain to your site. For wildcard certificates, Deploynix supports DNS validation through Cloudflare, DigitalOcean, AWS Route 53, and Vultr DNS.&lt;/p&gt;

&lt;p&gt;Deploynix also provides vanity domains (&lt;code&gt;*.deploynix.cloud&lt;/code&gt;) with pre-configured wildcard SSL certificates — useful for staging environments and quick deployments before your custom domain is configured.&lt;/p&gt;

&lt;p&gt;Certificate renewal is handled automatically. Deploynix monitors certificate expiration and renews before they expire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: The Deployment Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Zero-Downtime Deployments
&lt;/h3&gt;

&lt;p&gt;Deploynix uses a release-based deployment strategy that eliminates downtime:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new release directory is created&lt;/li&gt;
&lt;li&gt;Your repository is cloned or updated&lt;/li&gt;
&lt;li&gt;Composer dependencies are installed&lt;/li&gt;
&lt;li&gt;npm dependencies are installed and assets are built&lt;/li&gt;
&lt;li&gt;Your deploy script runs (migrations, cache clearing, etc.)&lt;/li&gt;
&lt;li&gt;The symlink switches from the old release to the new one&lt;/li&gt;
&lt;li&gt;PHP-FPM/Octane workers are reloaded&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The symlink switch is atomic — your application serves the old version until the exact moment it switches to the new one. There's no period where the application is partially deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Script
&lt;/h3&gt;

&lt;p&gt;Deploynix lets you define a custom deploy script that runs as part of the deployment process, inside the new release directory before the symlink swap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--optimize-autoloader&lt;/span&gt;
npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan queue:restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All steps complete before the new release goes live, ensuring users never see a partially prepared release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled Deployments
&lt;/h3&gt;

&lt;p&gt;Deploynix supports scheduling deployments for a future time. This is useful for coordinating releases with marketing launches or deploying during low-traffic windows. Scheduled deployments can be cancelled before their execution time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rollback
&lt;/h3&gt;

&lt;p&gt;When a deployment introduces a bug, Deploynix can roll back to any previous release instantly. The rollback switches the symlink back to a previous release directory — the same atomic operation as a forward deployment.&lt;/p&gt;

&lt;p&gt;Keep enough release directories to allow meaningful rollbacks. Deploynix retains configurable number of releases, automatically pruning older ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: Database Management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Running Migrations
&lt;/h3&gt;

&lt;p&gt;Run migrations as part of your deploy script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--force&lt;/code&gt; flag is required in production. Without it, Artisan prompts for confirmation — which hangs in an automated deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Backups
&lt;/h3&gt;

&lt;p&gt;Deploynix supports automated database backups to AWS S3, DigitalOcean Spaces, Wasabi, and any S3-compatible storage. Configure backup frequency, retention period, and storage destination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backup strategy recommendations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hourly backups for applications with high write volume&lt;/li&gt;
&lt;li&gt;Daily backups for most applications&lt;/li&gt;
&lt;li&gt;Store backups in a different region than your primary server&lt;/li&gt;
&lt;li&gt;Test restoring from backups periodically — an untested backup is not a backup&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database Optimization
&lt;/h3&gt;

&lt;p&gt;For production MySQL databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable the slow query log to identify performance problems&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; to 60-80% of available RAM on a dedicated database server&lt;/li&gt;
&lt;li&gt;Use connection pooling if your application creates many short-lived connections&lt;/li&gt;
&lt;li&gt;Monitor query performance through Deploynix's server metrics and Laravel Pulse&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 6: Queue Workers and Background Processing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Configuring Queue Workers
&lt;/h3&gt;

&lt;p&gt;Deploynix manages Supervisor configuration for your queue workers. Configure the number of worker processes, the queues they process, and restart policies.&lt;/p&gt;

&lt;p&gt;For most applications, start with 2-3 workers processing all queues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue: default,notifications,emails
Processes: 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As your application grows, dedicate workers to specific queues based on priority:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker 1: payments (1 process, high priority)
Worker 2: default,notifications (3 processes)
Worker 3: exports,reports (2 processes, can be slow)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Daemon Processes
&lt;/h3&gt;

&lt;p&gt;Beyond queue workers, you might need long-running processes: WebSocket servers (Laravel Reverb), schedule runners, or custom daemons. Deploynix manages these through its daemon feature, which configures Supervisor to keep them running and restart them on failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 7: Monitoring and Alerting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server Monitoring
&lt;/h3&gt;

&lt;p&gt;Deploynix provides real-time monitoring for every managed server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU usage: Sustained high CPU indicates resource contention or runaway processes&lt;/li&gt;
&lt;li&gt;Memory usage: Track consumption trends to predict when you need to scale&lt;/li&gt;
&lt;li&gt;Disk usage: Running out of disk space causes cascading failures&lt;/li&gt;
&lt;li&gt;Network I/O: Unusual spikes might indicate a DDoS attack or a deployment pulling large dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Health Alerts
&lt;/h3&gt;

&lt;p&gt;Configure health alerts to notify you when metrics cross thresholds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU above 90% for more than 10 minutes&lt;/li&gt;
&lt;li&gt;Memory above 85%&lt;/li&gt;
&lt;li&gt;Disk usage above 80%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deploynix sends alerts through your configured notification channels, giving you time to investigate before users are affected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application-Level Monitoring
&lt;/h3&gt;

&lt;p&gt;Complement Deploynix's infrastructure monitoring with application-level tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel Pulse for production performance trends, slow queries, and queue throughput&lt;/li&gt;
&lt;li&gt;Laravel Telescope (filtered) for capturing exceptions and failed jobs in production&lt;/li&gt;
&lt;li&gt;Custom health check endpoints that verify database, cache, queue, and external API connectivity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 8: Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Firewall Configuration
&lt;/h3&gt;

&lt;p&gt;Deploynix configures UFW (Uncomplicated Firewall) with sensible defaults: SSH (port 22), HTTP (port 80), and HTTPS (port 443) are open. Everything else is closed.&lt;/p&gt;

&lt;p&gt;Add custom rules for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database access from specific IP addresses (if you connect remotely)&lt;/li&gt;
&lt;li&gt;Application-specific ports (WebSockets on port 6001, etc.)&lt;/li&gt;
&lt;li&gt;Blocking known bad IP ranges&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SSH Security
&lt;/h3&gt;

&lt;p&gt;Deploynix provisions servers with key-based SSH authentication. Password authentication is disabled by default. Never re-enable it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Security Essentials
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keep PHP, Nginx, MySQL, and all system packages updated&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;composer audit&lt;/code&gt; regularly to check for vulnerable dependencies&lt;/li&gt;
&lt;li&gt;Use Sanctum for API authentication with granular token scopes&lt;/li&gt;
&lt;li&gt;Implement rate limiting on authentication and API endpoints&lt;/li&gt;
&lt;li&gt;Store sensitive values in environment variables, never in code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 9: Scaling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vertical Scaling (Scaling Up)
&lt;/h3&gt;

&lt;p&gt;The simplest scaling approach: give your server more resources. Deploynix makes this easy — resize your server through your cloud provider, and your application continues running with more CPU, RAM, and disk.&lt;/p&gt;

&lt;p&gt;Vertical scaling works until you hit the cloud provider's largest instance size or until cost becomes unreasonable. Most Laravel applications can serve thousands of concurrent users on a single well-configured server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Horizontal Scaling (Scaling Out)
&lt;/h3&gt;

&lt;p&gt;When a single server isn't enough, distribute the load across multiple servers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separate your database onto a dedicated server. This is usually the first scaling step and provides the biggest impact.&lt;/li&gt;
&lt;li&gt;Separate your cache (Valkey) onto a dedicated server. This prevents cache eviction during application memory pressure.&lt;/li&gt;
&lt;li&gt;Add worker servers for queue processing. This keeps background jobs from competing with web requests for CPU.&lt;/li&gt;
&lt;li&gt;Add web servers behind a load balancer. Deploynix's load balancer supports round robin, least connections, and IP hash methods.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Scaling Checklist
&lt;/h3&gt;

&lt;p&gt;Before scaling horizontally, ensure your application is stateless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sessions stored in Redis/Valkey (not file)&lt;/li&gt;
&lt;li&gt;Cache in Redis/Valkey (not file)&lt;/li&gt;
&lt;li&gt;File uploads on external storage (S3, not local disk)&lt;/li&gt;
&lt;li&gt;Queues using Redis/Valkey (not the database or sync driver)&lt;/li&gt;
&lt;li&gt;No local file writes that other servers need to access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your application writes anything to the local filesystem that other servers need to read, you'll have inconsistency across servers. Move shared state to external services before adding servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 10: The Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist for every new Laravel production deployment:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before First Deploy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;APP_DEBUG=false&lt;/code&gt; and &lt;code&gt;APP_ENV=production&lt;/code&gt; are set&lt;/li&gt;
&lt;li&gt;[ ] Database credentials use a strong, unique password&lt;/li&gt;
&lt;li&gt;[ ] Session, cache, and queue drivers are set to Redis/Valkey&lt;/li&gt;
&lt;li&gt;[ ] SSL certificate is provisioned&lt;/li&gt;
&lt;li&gt;[ ] Firewall rules are configured&lt;/li&gt;
&lt;li&gt;[ ] Backup schedule is configured and tested&lt;/li&gt;
&lt;li&gt;[ ] Health monitoring is enabled&lt;/li&gt;
&lt;li&gt;[ ] Queue workers are configured and running&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Every Deploy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Tests pass before deployment&lt;/li&gt;
&lt;li&gt;[ ] Migrations are tested against a copy of production data&lt;/li&gt;
&lt;li&gt;[ ] Assets are compiled for production&lt;/li&gt;
&lt;li&gt;[ ] Config, route, view, and event caches are refreshed&lt;/li&gt;
&lt;li&gt;[ ] Queue workers are restarted to pick up new code&lt;/li&gt;
&lt;li&gt;[ ] Deployment is verified by checking the application's health endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Post-Deploy Verification
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Application responds correctly to key user flows&lt;/li&gt;
&lt;li&gt;[ ] Queue workers are processing jobs&lt;/li&gt;
&lt;li&gt;[ ] No new errors appearing in logs&lt;/li&gt;
&lt;li&gt;[ ] Server metrics (CPU, memory, disk) are within normal ranges&lt;/li&gt;
&lt;li&gt;[ ] Scheduled tasks are running as expected&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Laravel deployment in 2026 is more powerful and more accessible than ever. Modern PHP runtimes like FrankenPHP deliver performance that would have been unthinkable a few years ago. Platforms like Deploynix eliminate the ops burden of server management, letting you focus on building your application.&lt;/p&gt;

&lt;p&gt;The key principles haven't changed: automate everything, monitor proactively, back up regularly, and scale intentionally. What has changed is the tooling — you no longer need a dedicated DevOps engineer to run production infrastructure. A solo developer with Deploynix can provision, deploy, monitor, and scale a Laravel application that serves millions of requests.&lt;/p&gt;

&lt;p&gt;Start simple. A single App server with zero-downtime deployments, automated backups, and health monitoring handles more traffic than most applications will ever see. Scale when the metrics tell you to, not when your anxiety does. And always, always test your backups.&lt;/p&gt;

&lt;p&gt;Your deployment pipeline is not a one-time setup — it's a living system that evolves with your application. Review it regularly, keep your dependencies updated, and invest in monitoring. The best deployment is the one you don't have to think about because everything is automated and every failure triggers an alert before your users notice.&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>devops</category>
      <category>ssl</category>
      <category>scaling</category>
    </item>
    <item>
      <title>Finding and Fixing Slow Queries in Laravel Before They Hit Production</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 15:58:15 +0000</pubDate>
      <link>https://dev.to/deploynix/finding-and-fixing-slow-queries-in-laravel-before-they-hit-production-o72</link>
      <guid>https://dev.to/deploynix/finding-and-fixing-slow-queries-in-laravel-before-they-hit-production-o72</guid>
      <description>&lt;p&gt;Every Laravel application has slow queries. The question is whether you find them before your users do.&lt;/p&gt;

&lt;p&gt;A query that takes 50ms on your local machine with 100 rows might take 5 seconds in production with 500,000 rows. A polymorphic relationship that works fine in development becomes a full table scan when the &lt;code&gt;morphables&lt;/code&gt; table hits a million records. An Eloquent scope that chains three &lt;code&gt;whereHas&lt;/code&gt; calls generates a nested subquery monster that brings your database server to its knees during peak traffic.&lt;/p&gt;

&lt;p&gt;The good news: Laravel gives you excellent tools to find these queries before they cause outages. The better news: once you find them, the fixes are usually straightforward. This post walks through a complete workflow — from local detection with Debugbar and Telescope, through query analysis with EXPLAIN, to production monitoring with Deploynix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Detect Slow Queries in Development
&lt;/h2&gt;

&lt;p&gt;The first line of defense is catching slow queries during development. Two tools make this effortless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Debugbar
&lt;/h3&gt;

&lt;p&gt;Debugbar is the fastest way to spot query problems. Install it as a dev dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require barryvdh/laravel-debugbar &lt;span class="nt"&gt;--dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every page load shows a toolbar at the bottom of your browser with the number of queries executed and their total time. Click the queries tab to see each individual query with its execution time and the file/line that triggered it.&lt;/p&gt;

&lt;p&gt;The critical metric here isn't the total query time — it's the query count. If loading a dashboard page executes 47 queries, you have an N+1 problem regardless of how fast each individual query runs. Those 47 queries will become 4,700 queries when your data grows.&lt;/p&gt;

&lt;p&gt;Look for these red flags in Debugbar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than 10-15 queries per page load&lt;/li&gt;
&lt;li&gt;Duplicate queries (the same query executed multiple times with different IDs)&lt;/li&gt;
&lt;li&gt;Queries without a &lt;code&gt;WHERE&lt;/code&gt; clause on large tables&lt;/li&gt;
&lt;li&gt;Queries that don't appear in your controller or view code (often caused by lazy-loaded relationships in Blade templates)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Laravel Telescope
&lt;/h3&gt;

&lt;p&gt;Telescope records every query your application executes and flags slow ones automatically. After installation, visit &lt;code&gt;/telescope/queries&lt;/code&gt; to see queries sorted by duration.&lt;/p&gt;

&lt;p&gt;Telescope is particularly useful for catching slow queries in API endpoints and queued jobs — places where Debugbar can't reach.&lt;/p&gt;

&lt;p&gt;Configure the slow query threshold in your Telescope service provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Telescope&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IncomingEntry&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;EntryType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slow'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slow-query'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default slow query threshold is 100ms, which is reasonable for most applications. Any query that takes longer than that deserves investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Analyze with EXPLAIN
&lt;/h2&gt;

&lt;p&gt;Once you've identified a slow query, the next step is understanding why it's slow. MySQL's &lt;code&gt;EXPLAIN&lt;/code&gt; command tells you exactly how the database plans to execute a query.&lt;/p&gt;

&lt;p&gt;Take the raw SQL from Debugbar or Telescope and run it with &lt;code&gt;EXPLAIN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows you the query execution plan. Here's what to look for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;type column:&lt;/strong&gt; This is the most important indicator.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ALL&lt;/code&gt; means a full table scan. On a large table, this is almost always the problem.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index&lt;/code&gt; means a full index scan — better than ALL, but still reads every row in the index.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;range&lt;/code&gt; means the query uses an index to select a range of rows. This is usually acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ref&lt;/code&gt; means the query uses an index to look up matching rows. This is good.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eq_ref&lt;/code&gt; means a unique index lookup. This is optimal.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;const&lt;/code&gt; means the query matches at most one row. This is the fastest possible lookup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;rows column:&lt;/strong&gt; The estimated number of rows MySQL will examine. If this number is close to the total row count of the table, you're doing a full scan even if the &lt;code&gt;type&lt;/code&gt; column says otherwise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extra column:&lt;/strong&gt; Watch for "Using filesort" (MySQL has to sort results without an index) and "Using temporary" (MySQL creates a temporary table). Both indicate potential performance problems on large datasets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running EXPLAIN from Laravel
&lt;/h3&gt;

&lt;p&gt;You can use Eloquent's &lt;code&gt;toSql()&lt;/code&gt; method to get the raw SQL, then run EXPLAIN through a database client. Or use the query builder's &lt;code&gt;explain()&lt;/code&gt; method directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the EXPLAIN output as a collection you can inspect or log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Common Slow Query Patterns and Fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Missing Index on WHERE Columns
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index on &lt;code&gt;status&lt;/code&gt;, MySQL scans every row in the orders table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a migration&lt;/span&gt;
&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Composite WHERE Without a Composite Index
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with individual indexes on &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;created_at&lt;/code&gt;, MySQL can only use one index per table in a simple query. It picks the most selective one and scans the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a composite index that covers all three columns in the correct order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Column order matters. Put equality conditions (&lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;) before range conditions (&lt;code&gt;created_at&lt;/code&gt;) and sort columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Using &lt;code&gt;whereHas&lt;/code&gt; with Complex Conditions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;whereHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rating'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;whereHas&lt;/code&gt; generates a correlated subquery that MySQL executes once per row in the outer table. On large tables, this is devastating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use a &lt;code&gt;JOIN&lt;/code&gt; instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'products.id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'reviews.product_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews.rating'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or denormalize the data by adding a &lt;code&gt;reviews_avg_rating&lt;/code&gt; column to the products table and updating it through an observer or event listener.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 4: Sorting Without an Index
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'views_count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there's no index covering both the &lt;code&gt;WHERE&lt;/code&gt; and &lt;code&gt;ORDER BY&lt;/code&gt; columns, MySQL fetches all matching rows, then sorts them in memory. The "Using filesort" flag in EXPLAIN confirms this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'views_count'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 5: Selecting All Columns When You Need Few
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Then in a Blade view:&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endforeach&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're loading every column (including potentially large text fields, JSON columns, and binary data) when you only need the name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces memory usage and network transfer between the database and application server. On tables with large &lt;code&gt;text&lt;/code&gt; or &lt;code&gt;json&lt;/code&gt; columns, the difference can be dramatic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 6: Unbounded Queries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a user has 50,000 activity log entries, this loads all 50,000 into memory at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use pagination or chunking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For display&lt;/span&gt;
&lt;span class="nv"&gt;$logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// For processing&lt;/span&gt;
&lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chunkById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$logs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Process 500 at a time&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Prevent Regressions with Automated Detection
&lt;/h2&gt;

&lt;p&gt;Laravel 12 lets you prevent lazy loading application-wide, turning N+1 problems into exceptions during development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bootstrap/app.php or a service provider&lt;/span&gt;
&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, you don't want exceptions crashing pages. Instead, log lazy loading violations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handleLazyLoadingViolationUsing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;get_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Lazy loading [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] on model [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$class&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a log trail of N+1 problems to fix without disrupting users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Monitor Queries in Production with Deploynix
&lt;/h2&gt;

&lt;p&gt;Development testing only goes so far. Your local database has a fraction of the data, the server has different resources, and traffic patterns create contention you can't simulate easily.&lt;/p&gt;

&lt;p&gt;Deploynix's server monitoring tracks your MySQL process's CPU and memory usage in real-time. When a slow query starts consuming resources, you'll see it reflected in the database server's metrics before it causes user-facing issues.&lt;/p&gt;

&lt;p&gt;Set up health alerts on your database server to notify you when MySQL CPU usage exceeds 80% for more than 5 minutes. This gives you a window to investigate before the query brings the server down.&lt;/p&gt;

&lt;p&gt;Combine Deploynix's infrastructure monitoring with Laravel Pulse's slow query tracking in production. Pulse tells you which queries are slow. Deploynix tells you whether those slow queries are actually impacting server resources. A query that takes 500ms but runs twice a day is a low priority fix. A query that takes 200ms but runs 10,000 times per hour is an emergency — and Deploynix's CPU graphs will make that obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Establish a Query Performance Budget
&lt;/h2&gt;

&lt;p&gt;Set concrete thresholds for your team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page load queries: Maximum 15 queries per page load&lt;/li&gt;
&lt;li&gt;API endpoint queries: Maximum 10 queries per request&lt;/li&gt;
&lt;li&gt;Individual query time: No single query over 100ms in development&lt;/li&gt;
&lt;li&gt;Total query time per request: Under 50ms for 95% of requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enforce these through automated testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'loads the dashboard in under 15 queries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertOk&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test fails when someone introduces an N+1 problem on the dashboard, catching it before code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Slow queries are inevitable as your application grows. What's not inevitable is letting them reach production undetected.&lt;/p&gt;

&lt;p&gt;Use Debugbar and Telescope to catch problems during development. Analyze suspicious queries with EXPLAIN to understand exactly why they're slow. Apply the common fixes — proper indexing, eager loading, query restructuring. Prevent regressions with &lt;code&gt;preventLazyLoading&lt;/code&gt; and query count tests. And monitor production with Deploynix to catch the queries that only become problems at scale.&lt;/p&gt;

&lt;p&gt;The developers who deploy confidently aren't the ones who write perfect queries. They're the ones who have systems in place to find and fix imperfect queries before users feel the pain.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>databasequeries</category>
      <category>laravel</category>
      <category>mysql</category>
    </item>
    <item>
      <title>N+1 Query Detection and Prevention in Laravel Production Apps</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 09:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/n1-query-detection-and-prevention-in-laravel-production-apps-2lg</link>
      <guid>https://dev.to/deploynix/n1-query-detection-and-prevention-in-laravel-production-apps-2lg</guid>
      <description>&lt;p&gt;The N+1 query problem is the most common performance issue in Laravel applications, and it's the easiest to introduce accidentally. A developer adds &lt;code&gt;$post-&amp;gt;author-&amp;gt;name&lt;/code&gt; in a Blade template, and suddenly a page that loaded 10 posts now executes 11 database queries instead of 2. Scale that to 100 posts and you have 101 queries. Scale to a thousand and your database server is on fire.&lt;/p&gt;

&lt;p&gt;What makes N+1 problems particularly insidious is that they're invisible during development. Your local database with 20 records responds instantly whether you execute 1 query or 100. The problem only becomes apparent in production where tables have millions of rows, query latency is higher, and database connections are shared across hundreds of concurrent requests.&lt;/p&gt;

&lt;p&gt;This post covers everything you need to detect N+1 queries, prevent them from being introduced, and monitor for them in production Laravel applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the N+1 Problem
&lt;/h2&gt;

&lt;p&gt;An N+1 query occurs when you load a collection of N models and then access a relationship on each model individually, triggering a separate query for each one.&lt;/p&gt;

&lt;p&gt;Here's the classic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Controller&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 1 query: SELECT * FROM posts&lt;/span&gt;

&lt;span class="c1"&gt;// Blade template&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="c1"&gt;// N queries: SELECT * FROM users WHERE id = ?&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endforeach&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first query loads all posts. Then, for each post, Laravel lazy-loads the &lt;code&gt;author&lt;/code&gt; relationship with a separate query. With 50 posts, that's 51 queries total.&lt;/p&gt;

&lt;p&gt;The fix is eager loading — tell Laravel to load all authors in a single query upfront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 2 queries total&lt;/span&gt;
&lt;span class="c1"&gt;// Query 1: SELECT * FROM posts&lt;/span&gt;
&lt;span class="c1"&gt;// Query 2: SELECT * FROM users WHERE id IN (1, 2, 3, ...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two queries instead of 51. The concept is simple. The challenge is applying it consistently across a growing codebase where relationships are accessed in controllers, views, components, API resources, and queued jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention: Laravel's preventLazyLoading
&lt;/h2&gt;

&lt;p&gt;Laravel provides a built-in mechanism to catch N+1 problems during development. When enabled, any lazy-loaded relationship throws an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In a service provider's boot method&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the &lt;code&gt;$post-&amp;gt;author-&amp;gt;name&lt;/code&gt; call without eager loading throws a &lt;code&gt;LazyLoadingViolationException&lt;/code&gt; instead of silently executing an extra query. This forces developers to explicitly eager-load every relationship they access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Violations in Production
&lt;/h3&gt;

&lt;p&gt;You don't want exceptions crashing pages in production. Instead, log violations so you can fix them without disrupting users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handleLazyLoadingViolationUsing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"N+1 detected: lazy loading [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] on ["&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;get_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach gives you the best of both worlds: hard failures in development that force immediate fixes, and silent logging in production that reveals problems you missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  When preventLazyLoading Gets in the Way
&lt;/h3&gt;

&lt;p&gt;There are legitimate cases where lazy loading is acceptable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Artisan commands that process a single model and access one relationship&lt;/li&gt;
&lt;li&gt;Queue jobs that operate on a known small dataset&lt;/li&gt;
&lt;li&gt;Tinker sessions during debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can selectively allow lazy loading on specific models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// On a specific model&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Setting&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Settings table is tiny; lazy loading is fine&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nv"&gt;$preventLazyLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or disable it temporarily in specific contexts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withoutPreventing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Lazy loading is allowed in this closure&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Eager Loading Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Eager Loading
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;with()&lt;/code&gt; method accepts a single relationship or an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'comments'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nested Eager Loading
&lt;/h3&gt;

&lt;p&gt;Load relationships of relationships using dot notation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'comments.author'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'author.profile'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Loads: posts, comments, comment authors, post authors, author profiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Constrained Eager Loading
&lt;/h3&gt;

&lt;p&gt;Filter or limit the eager-loaded relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'comments'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Laravel 12 supports &lt;code&gt;limit()&lt;/code&gt; on eager loads natively — no external packages needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional Eager Loading
&lt;/h3&gt;

&lt;p&gt;Load relationships only when certain conditions are met:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$includeComments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comments'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$includeAuthor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Eager Loading on Existing Collections
&lt;/h3&gt;

&lt;p&gt;If you already have a collection and need to load a relationship after the fact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Later, you realize you need authors&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or load only if not already loaded&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadMissing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;loadMissing()&lt;/code&gt; is particularly useful in deep call stacks where you're not sure if a relationship was already eager-loaded upstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default Eager Loading on Models
&lt;/h3&gt;

&lt;p&gt;If a relationship is always needed when a model is loaded, define it as a default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$with&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this sparingly. It means every &lt;code&gt;Post::all()&lt;/code&gt;, &lt;code&gt;Post::find()&lt;/code&gt;, and &lt;code&gt;Post::where(...)&lt;/code&gt; call loads the author relationship, even when it's not needed. This can turn a simple count query into a heavy join operation.&lt;/p&gt;

&lt;p&gt;A better approach is to define scopes for common access patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scopeWithFeedRelations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Builder&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Builder&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'comments'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withFeedRelations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detecting N+1 in Existing Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query Counting in Tests
&lt;/h3&gt;

&lt;p&gt;Write tests that enforce query budgets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'loads the post index without N+1 queries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/posts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertOk&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If loading 20 posts requires more than 10 queries, something is lazy loading when it shouldn't be. This test catches regressions when someone adds a new relationship access in the view without updating the controller's eager loading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Laravel Debugbar
&lt;/h3&gt;

&lt;p&gt;Debugbar shows duplicate queries in its queries tab. If you see &lt;code&gt;SELECT * FROM users WHERE id = ?&lt;/code&gt; repeated 20 times with different IDs, that's an N+1 problem on the &lt;code&gt;author&lt;/code&gt; relationship.&lt;/p&gt;

&lt;p&gt;Debugbar also shows the file and line where each query was triggered. This makes it trivial to trace the lazy load back to the specific Blade template line or component that caused it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Telescope's Query Tab
&lt;/h3&gt;

&lt;p&gt;Telescope groups queries by request. Look for requests with high query counts — anything above 20-30 queries per request warrants investigation. Sort by query count to find the worst offenders first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common N+1 Traps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trap 1: Blade Components That Access Relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/View/Components/PostCard.php&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'components.post-card'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// components/post-card.blade.php&lt;/span&gt;



&lt;span class="c1"&gt;## {{ $post-&amp;gt;title }}&lt;/span&gt;

    &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nc"&gt;N&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;eager&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component looks harmless. The N+1 is hidden because the relationship access happens in the view, far from the controller that loaded the posts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Ensure the parent view or controller eager-loads the relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trap 2: API Resources with Nested Relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JsonResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'author'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// N+1&lt;/span&gt;
            &lt;span class="s1"&gt;'comments_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// N+1 AND loads all comments&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Eager-load in the controller and use &lt;code&gt;withCount&lt;/code&gt; for counts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comments'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// In the resource:&lt;/span&gt;
&lt;span class="s1"&gt;'comments_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comments_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Uses the aggregate, no extra query&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trap 3: Accessors That Touch Relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;totalWithTax&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Attribute&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Attribute&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;taxRate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="c1"&gt;// Accessing $this-&amp;gt;taxRate triggers a lazy load&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accessors are called during serialization, in Blade templates, and in API resources. Each call triggers the relationship query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Eager-load &lt;code&gt;taxRate&lt;/code&gt; wherever you use this accessor, or cache the relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;totalWithTax&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Attribute&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Attribute&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;relationLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'taxRate'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;taxRate&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trap 4: Queued Jobs Processing Collections
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendWeeklyDigest&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldQueue&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'digest_enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Each of these triggers a query&lt;/span&gt;
                &lt;span class="nv"&gt;$recentPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WeeklyDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$recentPosts&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'digest_enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'subscriptions.posts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subWeek&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$recentPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WeeklyDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$recentPosts&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring in Production
&lt;/h2&gt;

&lt;p&gt;Even with &lt;code&gt;preventLazyLoading&lt;/code&gt; and test coverage, N+1 problems can slip through. Monitor query counts in production using multiple layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Pulse
&lt;/h3&gt;

&lt;p&gt;Pulse tracks slow queries and high-frequency queries in production. A query that appears hundreds of times per minute with slight parameter variations is likely an N+1 problem. Check Pulse's slow queries dashboard regularly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploynix Server Monitoring
&lt;/h3&gt;

&lt;p&gt;On Deploynix, monitor your database server's CPU and connection count. A sudden increase in active connections often correlates with N+1 queries — each lazy-loaded query opens and closes a connection (or holds one from the pool). If your connection count spikes during peak traffic, investigate the most active endpoints for N+1 issues.&lt;/p&gt;

&lt;p&gt;Deploynix's real-time monitoring dashboard shows MySQL process CPU usage alongside your application server's metrics. When the database CPU spikes but the application CPU stays flat, the cause is almost always excessive queries — and N+1 is the most likely culprit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Middleware for Query Counting
&lt;/h3&gt;

&lt;p&gt;Add middleware that logs requests exceeding a query threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueryCountMiddleware&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"High query count: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; queries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'method'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it globally in development or for specific route groups in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;N+1 queries are the single most impactful performance problem in Laravel applications, and they're entirely preventable. Enable &lt;code&gt;preventLazyLoading&lt;/code&gt; to catch them during development. Write query-count tests to prevent regressions. Use &lt;code&gt;with()&lt;/code&gt;, &lt;code&gt;loadMissing()&lt;/code&gt;, and &lt;code&gt;withCount()&lt;/code&gt; consistently.&lt;/p&gt;

&lt;p&gt;In production, monitor query patterns through Pulse and watch for database CPU spikes on your Deploynix dashboard. The combination of prevention at the code level and detection at the infrastructure level means N+1 problems get caught before they become outages.&lt;/p&gt;

&lt;p&gt;The goal isn't zero lazy loading — it's intentional lazy loading. Every relationship access should be a deliberate choice, not an accidental database round trip hiding in a Blade component three levels deep.&lt;/p&gt;

</description>
      <category>n1</category>
      <category>eloquent</category>
      <category>laravelperformance</category>
      <category>database</category>
    </item>
    <item>
      <title>Laravel Telescope vs. Pulse vs. Deploynix Monitoring: Which Do You Need?</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 00:32:04 +0000</pubDate>
      <link>https://dev.to/deploynix/laravel-telescope-vs-pulse-vs-deploynix-monitoring-which-do-you-need-5dal</link>
      <guid>https://dev.to/deploynix/laravel-telescope-vs-pulse-vs-deploynix-monitoring-which-do-you-need-5dal</guid>
      <description>&lt;p&gt;Laravel developers in 2026 have three mature monitoring options, each built for a different layer of the stack. Telescope gives you a microscope into individual requests. Pulse gives you a dashboard for application-level trends. Deploynix monitoring gives you infrastructure-level visibility with health alerting.&lt;/p&gt;

&lt;p&gt;The question isn't which one is best. The question is which combination you need — because they're not competitors. They're complementary tools that, when used together, give you complete observability from the server hardware up through individual Eloquent queries.&lt;/p&gt;

&lt;p&gt;This post breaks down what each tool does, where they overlap, how they differ in performance overhead, and which setup makes sense based on your application's size and stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Tool Actually Does
&lt;/h2&gt;

&lt;p&gt;Before comparing features, it helps to understand the fundamental design philosophy behind each tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Telescope: The Development Debugger
&lt;/h3&gt;

&lt;p&gt;Telescope is a debugging assistant. It records every request, exception, database query, queued job, mail, notification, cache operation, and scheduled task that your application processes. You browse this data through a web dashboard that lets you inspect individual entries in detail.&lt;/p&gt;

&lt;p&gt;Telescope answers questions like: What exact SQL queries did this request execute? What was the payload of this queued job that failed? Why did this notification take 3 seconds to send?&lt;/p&gt;

&lt;p&gt;It shines during development and staging. In production, its recording overhead and storage requirements make it impractical for high-traffic applications unless heavily filtered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Pulse: The Application Dashboard
&lt;/h3&gt;

&lt;p&gt;Pulse is an application performance monitoring tool. Instead of recording every individual event, it aggregates data into trends and surfaces the metrics that matter: slowest requests, most frequent queries, queue throughput, cache hit rates, and active user counts.&lt;/p&gt;

&lt;p&gt;Pulse answers questions like: Which endpoints are consistently slow this week? Are my queue workers keeping up with job volume? What's my cache hit ratio trending toward?&lt;/p&gt;

&lt;p&gt;It's designed for production use. Its aggregation approach keeps storage requirements manageable, and its sampling capabilities let you control the performance overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploynix Monitoring: The Infrastructure Layer
&lt;/h3&gt;

&lt;p&gt;Deploynix monitoring operates at the server and site level. It tracks CPU usage, memory consumption, disk I/O, network throughput, and disk space across all your managed servers. It provides real-time alerts when metrics cross thresholds you define.&lt;/p&gt;

&lt;p&gt;Deploynix answers questions like: Is the server running out of memory? Has CPU usage been sustained above 90% for the last hour? Is the application responding to HTTP requests? When did disk usage cross the warning threshold?&lt;/p&gt;

&lt;p&gt;It doesn't know about your Laravel application's internals — it doesn't see individual queries or jobs. But it sees everything happening at the operating system level, which is exactly the layer you need visibility into when your application monitoring tools report that "everything looks fine" but users are experiencing slowness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;p&gt;Here's a direct comparison of what each tool monitors:&lt;/p&gt;

&lt;p&gt;Capability&lt;/p&gt;

&lt;p&gt;Telescope&lt;/p&gt;

&lt;p&gt;Pulse&lt;/p&gt;

&lt;p&gt;Deploynix&lt;/p&gt;

&lt;p&gt;Individual request inspection&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Request performance trends&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Database query logging&lt;/p&gt;

&lt;p&gt;Yes (all)&lt;/p&gt;

&lt;p&gt;Yes (slow)&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Exception tracking&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Queue job inspection&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Cache operations&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;CPU/Memory monitoring&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Disk space alerts&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Server health monitoring&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Real-time server metrics&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Mail/notification logging&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Model event tracking&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Gate/policy inspection&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Health check alerting&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;User activity tracking&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Performance percentiles&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;The overlap is minimal. Telescope and Pulse both track queries and exceptions, but in fundamentally different ways. Telescope records every single query for later inspection. Pulse aggregates query performance into trends and highlights outliers. Deploynix doesn't touch queries at all — it watches the MySQL process's resource consumption from the OS level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Overhead
&lt;/h2&gt;

&lt;p&gt;This is where the differences matter most in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telescope's Overhead
&lt;/h3&gt;

&lt;p&gt;Telescope's default configuration records everything. Every request generates multiple database inserts for the request entry, query entries, model entries, and any other watchers you have enabled. On a high-traffic application, this creates significant database load.&lt;/p&gt;

&lt;p&gt;You can mitigate this with selective recording:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Providers/TelescopeServiceProvider.php&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Telescope&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IncomingEntry&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'local'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isReportableException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isSlowQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isScheduledTask&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with filtering, Telescope adds measurable overhead per request because the filtering logic itself runs on every request. For applications handling hundreds of requests per second, this matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; Enable Telescope fully in local and staging environments. In production, either disable it entirely or filter aggressively to only capture failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulse's Overhead
&lt;/h3&gt;

&lt;p&gt;Pulse was designed for production from day one. It uses an aggregation pipeline that batches metrics before writing them to the database, reducing write frequency dramatically compared to Telescope.&lt;/p&gt;

&lt;p&gt;Pulse also supports sampling — you can tell it to only record every Nth request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/pulse.php&lt;/span&gt;
&lt;span class="s1"&gt;'sample_rate'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Record 10% of requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a 10% sample rate, Pulse gives you statistically meaningful trend data with a fraction of the overhead. The aggregated storage is also much smaller — Pulse automatically prunes old data based on configurable retention periods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; Run Pulse in production with sampling enabled. A 10-25% sample rate provides excellent trend data for most applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploynix's Overhead
&lt;/h3&gt;

&lt;p&gt;Deploynix monitoring runs at the OS level, completely outside your Laravel application. It uses lightweight system agents that collect metrics from &lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;sysstat&lt;/code&gt;, and similar system interfaces. Your application doesn't execute a single extra line of PHP.&lt;/p&gt;

&lt;p&gt;The HTTP health check adds one request every 30-60 seconds (configurable), which is negligible on any application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; Always enable Deploynix monitoring. There's no meaningful overhead to your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Setups by Application Size
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Solo Developer / Side Project
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring only.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you're launching an MVP or running a small project, you don't need application-level monitoring yet. Deploynix's built-in server monitoring and health alerts will tell you if something goes catastrophically wrong. Use Telescope locally during development to debug issues, but don't bother installing it in production.&lt;/p&gt;

&lt;p&gt;At this stage, your time is better spent building features than configuring monitoring dashboards. Deploynix handles the infrastructure concerns so you don't have to SSH into servers to check disk space or CPU usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Growing Application (1,000+ Daily Users)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring + Pulse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once your application serves real traffic consistently, you need trend data. Pulse shows you which endpoints are slowing down, whether your cache strategy is working, and if queue processing is keeping pace with demand.&lt;/p&gt;

&lt;p&gt;Install Pulse with a 25% sample rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'sample_rate'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you solid performance data without measurable overhead. Combined with Deploynix's server metrics, you can correlate application slowness (Pulse) with infrastructure bottlenecks (Deploynix).&lt;/p&gt;

&lt;p&gt;For example, Pulse might show that your &lt;code&gt;/api/reports&lt;/code&gt; endpoint's P95 response time jumped from 200ms to 1,200ms. Deploynix's server monitoring might simultaneously show that MySQL's CPU usage spiked to 95%. Now you know the problem is a database query issue on that endpoint, not a code regression or memory leak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Application (10,000+ Daily Users)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring + Pulse + Telescope (filtered).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this scale, you need all three layers. Pulse gives you the trends. Deploynix gives you infrastructure alerting. And Telescope — heavily filtered to only capture failures and slow queries — gives you the detailed forensics you need when something goes wrong.&lt;/p&gt;

&lt;p&gt;Configure Telescope to only record problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Telescope&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IncomingEntry&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isReportableException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
           &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
           &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
           &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;EntryType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SLOW_QUERY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Pulse's dashboard shows a spike in slow requests, switch to Telescope to inspect the individual queries and request payloads that caused it. When Deploynix alerts you to high memory usage, check Pulse to see if a particular endpoint is processing larger payloads than usual.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Traffic Application (100,000+ Daily Users)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring + Pulse (low sample rate) + dedicated APM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At very high traffic volumes, consider adding a dedicated APM service (New Relic, Datadog, or similar) alongside Pulse and Deploynix. Telescope's database storage model doesn't scale well at this level even with aggressive filtering.&lt;/p&gt;

&lt;p&gt;Run Pulse at a 5-10% sample rate for trend data. Use Deploynix for infrastructure monitoring and alerting. Let your APM handle the detailed request tracing that Telescope would otherwise provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Them Work Together
&lt;/h2&gt;

&lt;p&gt;The real power emerges when you use these tools as a unified observability stack rather than isolated dashboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Triage workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploynix alerts you that a server's CPU has been above 90% for 10 minutes.&lt;/li&gt;
&lt;li&gt;Open Pulse to check which endpoints or queues are consuming the most resources.&lt;/li&gt;
&lt;li&gt;If Pulse points to a specific endpoint, open Telescope to inspect recent requests to that endpoint and find the problematic query or operation.&lt;/li&gt;
&lt;li&gt;Fix the issue, deploy through Deploynix, and watch all three dashboards confirm the resolution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Performance investigation workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pulse shows P95 response time increasing over the past week.&lt;/li&gt;
&lt;li&gt;Drill into Pulse's slow queries tab to identify which queries are degrading.&lt;/li&gt;
&lt;li&gt;Check Deploynix's server metrics to see if the database server's disk I/O is saturated.&lt;/li&gt;
&lt;li&gt;If disk I/O is the bottleneck, use Deploynix to scale up the database server or add read replicas.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuration Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Don't Run Telescope and Pulse on the Same Database
&lt;/h3&gt;

&lt;p&gt;Both tools write monitoring data to your database. In production, configure them to use a separate database connection to avoid their writes competing with your application's queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/pulse.php&lt;/span&gt;
&lt;span class="s1"&gt;'storage'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'connection'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pulse'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/database.php&lt;/span&gt;
&lt;span class="s1"&gt;'pulse'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PULSE_DB_HOST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PULSE_DB_DATABASE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pulse'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set Retention Policies
&lt;/h3&gt;

&lt;p&gt;Both Telescope and Pulse accumulate data. Configure pruning to prevent storage from growing unbounded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Telescope: prune entries older than 48 hours&lt;/span&gt;
&lt;span class="nv"&gt;$schedule&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'telescope:prune --hours=48'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;daily&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Pulse: configure in config/pulse.php&lt;/span&gt;
&lt;span class="s1"&gt;'retain'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'entries'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 7 days in minutes&lt;/span&gt;
    &lt;span class="s1"&gt;'aggregates'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 90 days in minutes&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Deploynix's Monitoring for Alerting
&lt;/h3&gt;

&lt;p&gt;Neither Telescope nor Pulse has built-in alerting. They're dashboards you check, not systems that notify you. Deploynix fills this gap with health alerts that trigger notifications when your servers or sites enter unhealthy states.&lt;/p&gt;

&lt;p&gt;Pair Deploynix's server-level health alerts with an external uptime monitoring service that polls your custom health check endpoint, and you get alerts that combine infrastructure metrics with application health data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Telescope, Pulse, and Deploynix monitoring aren't competing solutions — they're complementary layers of a complete observability stack. Telescope gives you the microscope for debugging individual issues. Pulse gives you the dashboard for tracking application performance trends. Deploynix gives you the infrastructure monitoring and alerting foundation that the other two are built on top of.&lt;/p&gt;

&lt;p&gt;Start with Deploynix monitoring on every server. Add Pulse when you need application-level trend data. Layer in Telescope when you need forensic debugging capabilities in production. Together, they give you visibility from the CPU cores up through individual database queries — and the confidence that you'll know about problems before your users do.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>monitoring</category>
      <category>telescope</category>
      <category>observability</category>
    </item>
    <item>
      <title>Custom Health Check Endpoints for Laravel: Beyond 200 OK</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Thu, 23 Apr 2026 10:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/custom-health-check-endpoints-for-laravel-beyond-200-ok-44n9</link>
      <guid>https://dev.to/deploynix/custom-health-check-endpoints-for-laravel-beyond-200-ok-44n9</guid>
      <description>&lt;p&gt;Every Laravel application ships with a default route that returns a 200 OK response. It tells you the web server is running. It tells you PHP is alive. And it tells you absolutely nothing about whether your application is actually healthy.&lt;/p&gt;

&lt;p&gt;A returning customer can't complete checkout if your Redis connection dropped. Your API consumers get cryptic 500 errors when the database connection pool is exhausted. Your queue workers silently stopped processing jobs three hours ago, and nobody noticed because your uptime monitor keeps reporting "all clear."&lt;/p&gt;

&lt;p&gt;Sound familiar? The problem is that most health checks are superficial. They verify the application process is running without confirming the application is actually working. In this post, we'll build a comprehensive health check endpoint that validates every critical dependency your Laravel application relies on — and integrate it with Deploynix's monitoring to get alerts before your users notice problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Simple 200 OK Isn't Enough
&lt;/h2&gt;

&lt;p&gt;A basic health check route like this is dangerously misleading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/health'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route will return 200 even when your database is down, your cache server is unreachable, your queue workers have crashed, your disk is 99% full, and your payment gateway API is timing out.&lt;/p&gt;

&lt;p&gt;A proper health check should verify the entire dependency chain your application needs to function correctly. When any critical component fails, your monitoring system should know about it immediately — not when a customer opens a support ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing Your Health Check Architecture
&lt;/h2&gt;

&lt;p&gt;Before writing code, think about what "healthy" means for your specific application. Most Laravel applications depend on some combination of database connectivity, cache availability, queue processing, filesystem access, and external API reachability.&lt;/p&gt;

&lt;p&gt;We'll build a health check system with two tiers: a lightweight liveness probe that confirms the application process is running, and a comprehensive readiness probe that validates all dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/api.php&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/health/live'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HealthCheckController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'liveness'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/health/ready'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HealthCheckController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'readiness'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The liveness endpoint stays simple and fast. Load balancers and container orchestrators use it to determine if the process should be restarted. The readiness endpoint does the heavy lifting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Health Check Controller
&lt;/h2&gt;

&lt;p&gt;Start by generating a controller dedicated to health checks:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravelmonitoring</category>
      <category>laravelhealthchecks</category>
      <category>laraveldevops</category>
      <category>deploynix</category>
    </item>
    <item>
      <title>Laravel Deployment for Beginners: What Actually Happens When You Click Deploy</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Wed, 22 Apr 2026 22:00:08 +0000</pubDate>
      <link>https://dev.to/deploynix/laravel-deployment-for-beginners-what-actually-happens-when-you-click-deploy-1b0n</link>
      <guid>https://dev.to/deploynix/laravel-deployment-for-beginners-what-actually-happens-when-you-click-deploy-1b0n</guid>
      <description>&lt;p&gt;You have been building your Laravel application on your laptop for weeks. Everything works perfectly on &lt;code&gt;localhost&lt;/code&gt;. The features are done, the pages look great, and your tests pass. Now you want to put it on the internet so real people can use it. You click "Deploy" in Deploynix, a progress bar fills up, and 60 seconds later your application is live.&lt;/p&gt;

&lt;p&gt;But what actually happened during those 60 seconds? What did Deploynix do to take your code from a GitHub repository and turn it into a working website? Understanding the deployment process demystifies one of the most intimidating parts of web development. Once you know what is happening under the hood, deployments stop being scary and start being routine.&lt;/p&gt;

&lt;p&gt;This guide walks through every step of a Laravel deployment in plain language. No jargon, no assumptions about your experience level. If you can build a Laravel app, you can understand how it gets deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before Deployment: What Your Server Looks Like
&lt;/h2&gt;

&lt;p&gt;Before you ever deploy your application, Deploynix has already set up your server with everything Laravel needs to run. Think of your server as a computer in a data center that is always on, always connected to the internet, and always waiting to serve your website.&lt;/p&gt;

&lt;p&gt;When Deploynix provisions a server, it installs and configures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The operating system: Ubuntu Linux, which is the most common operating system for web servers.&lt;/li&gt;
&lt;li&gt;PHP: The programming language that Laravel is written in. Deploynix installs the exact version your application needs.&lt;/li&gt;
&lt;li&gt;A web server (Nginx): Software that listens for incoming web requests and routes them to your Laravel application. When someone types your domain name into their browser, Nginx is the first piece of software that responds.&lt;/li&gt;
&lt;li&gt;A database (MySQL, MariaDB, or PostgreSQL): Where your application stores its data, such as user accounts, posts, orders, and anything else you save to the database.&lt;/li&gt;
&lt;li&gt;Composer: The tool that manages your PHP dependencies, which are the packages and libraries your Laravel application uses.&lt;/li&gt;
&lt;li&gt;Node.js and npm: Tools for compiling your frontend assets, things like CSS and JavaScript that your browser needs to display your pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this setup happens once, when you first provision the server. After that, deploying your application is about getting your code onto this server and configuring it to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Getting Your Code from GitHub
&lt;/h2&gt;

&lt;p&gt;The first thing that happens when you click Deploy is that Deploynix copies your application's code from your Git repository (GitHub, GitLab, or Bitbucket) to your server.&lt;/p&gt;

&lt;p&gt;You have been pushing code to your repository every time you commit changes. Your repository contains everything that makes up your application: the PHP files, the Blade templates, the JavaScript, the configuration files, and the migration files. It is the single source of truth for what your application looks like at any given moment.&lt;/p&gt;

&lt;p&gt;Deploynix connects to your repository and downloads the latest version of your code to the server. This is similar to what happens when you run &lt;code&gt;git clone&lt;/code&gt; on your laptop, except Deploynix is doing it on your server.&lt;/p&gt;

&lt;p&gt;But here is an important detail: Deploynix does not just dump the code into the same folder every time. It creates a brand new folder for each deployment. If today is your fifth deployment, there are five separate folders on your server, one for each version of your code. We will explain why this matters in a later step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Installing PHP Dependencies
&lt;/h2&gt;

&lt;p&gt;Your Laravel application depends on dozens of packages written by other developers. Things like the Laravel framework itself, the database driver, the authentication system, and any other packages you have added with &lt;code&gt;composer require&lt;/code&gt;. These dependencies are listed in your &lt;code&gt;composer.json&lt;/code&gt; file but are not stored in your Git repository (the &lt;code&gt;vendor&lt;/code&gt; folder is in your &lt;code&gt;.gitignore&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;After downloading your code, Deploynix runs &lt;code&gt;composer install&lt;/code&gt; on the server. This reads your &lt;code&gt;composer.json&lt;/code&gt; and &lt;code&gt;composer.lock&lt;/code&gt; files, downloads every package your application needs, and puts them in the &lt;code&gt;vendor&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;This step uses the &lt;code&gt;composer.lock&lt;/code&gt; file, not just &lt;code&gt;composer.json&lt;/code&gt;. The lock file records the exact version of every package that was installed on your development machine. This ensures that the server installs the same versions you tested with, not newer versions that might behave differently. Consistency between your development environment and production is critical.&lt;/p&gt;

&lt;p&gt;In production, Deploynix passes the &lt;code&gt;--no-dev&lt;/code&gt; flag to Composer, which tells it to skip development-only dependencies. Things like Pest (your testing framework) and Laravel Pint (your code formatter) are not needed on a production server, so skipping them makes the installation faster and the application lighter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Installing and Building Frontend Assets
&lt;/h2&gt;

&lt;p&gt;If your application uses Tailwind CSS, Alpine.js, or any other frontend tools managed through npm, the next step is installing those JavaScript dependencies and compiling your assets.&lt;/p&gt;

&lt;p&gt;Deploynix runs &lt;code&gt;npm install&lt;/code&gt; (or &lt;code&gt;npm ci&lt;/code&gt;) to download the JavaScript packages listed in your &lt;code&gt;package.json&lt;/code&gt; file. Then it runs &lt;code&gt;npm run build&lt;/code&gt; to compile everything into the final CSS and JavaScript files that browsers can understand.&lt;/p&gt;

&lt;p&gt;During development, you might have been running &lt;code&gt;npm run dev&lt;/code&gt;, which watches for file changes and recompiles on the fly. In production, &lt;code&gt;npm run build&lt;/code&gt; creates optimized, minified versions of your assets. Minification removes whitespace, shortens variable names, and compresses the files so they are as small as possible. Smaller files mean faster page loads for your users.&lt;/p&gt;

&lt;p&gt;The compiled files typically end up in your &lt;code&gt;public/build&lt;/code&gt; folder. These are the files that Nginx will serve directly to browsers when they request your CSS and JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Setting Up the Environment File
&lt;/h2&gt;

&lt;p&gt;Your Laravel application uses a &lt;code&gt;.env&lt;/code&gt; file to store configuration that varies between environments. On your laptop, the &lt;code&gt;.env&lt;/code&gt; file points to your local database, uses the &lt;code&gt;local&lt;/code&gt; app environment, and shows detailed error messages. In production, the configuration is different.&lt;/p&gt;

&lt;p&gt;Deploynix manages your production environment variables through its dashboard. You set values like your database credentials, your application key, your mail server settings, and any API keys your application needs. Deploynix stores these securely and makes them available to your application through the &lt;code&gt;.env&lt;/code&gt; file on the server.&lt;/p&gt;

&lt;p&gt;Some important differences between your local &lt;code&gt;.env&lt;/code&gt; and your production &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;APP_ENV&lt;/code&gt; is set to &lt;code&gt;production&lt;/code&gt; instead of &lt;code&gt;local&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_DEBUG&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; so that error details are not shown to users. In development, seeing a full stack trace is helpful. In production, it would expose sensitive information.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_URL&lt;/code&gt; points to your actual domain name instead of &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Database credentials point to your production database server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_KEY&lt;/code&gt; is a unique encryption key that Laravel uses to encrypt cookies, sessions, and other sensitive data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file is never stored in your Git repository (it is in &lt;code&gt;.gitignore&lt;/code&gt;). This is intentional. You do not want production database passwords in your code repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Running Database Migrations
&lt;/h2&gt;

&lt;p&gt;Your application's database starts empty. The structure of your database (the tables, the columns, the indexes) is defined in migration files. Migrations are PHP files in your &lt;code&gt;database/migrations&lt;/code&gt; folder that describe each change to your database schema.&lt;/p&gt;

&lt;p&gt;When Deploynix deploys your application, it runs &lt;code&gt;php artisan migrate&lt;/code&gt; to apply any new migrations. This command looks at which migrations have already been run (Laravel tracks this in a special &lt;code&gt;migrations&lt;/code&gt; table in your database) and runs only the new ones.&lt;/p&gt;

&lt;p&gt;For example, if your previous deployment had 15 migrations and you have since added a 16th migration that adds a &lt;code&gt;phone_number&lt;/code&gt; column to the &lt;code&gt;users&lt;/code&gt; table, the migrate command will only run that 16th migration. The other 15 are already reflected in the database.&lt;/p&gt;

&lt;p&gt;Migrations are run in order, based on the timestamp in their filename. This ensures that the database structure evolves predictably and consistently, whether you are deploying for the first time or the hundredth time.&lt;/p&gt;

&lt;p&gt;This step is one of the few parts of deployment that can cause problems if something goes wrong. A migration that fails halfway through can leave your database in an inconsistent state. This is why it is important to test your migrations thoroughly before deploying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Caching Configuration, Routes, and Views
&lt;/h2&gt;

&lt;p&gt;Laravel is a feature-rich framework, and loading all of its configuration, routes, and views from scratch on every request takes time. To make your application faster in production, Deploynix runs several caching commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;php artisan config:cache&lt;/code&gt; compiles all of your configuration files into a single cached file. Instead of reading dozens of configuration files on every request, Laravel reads one file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;php artisan route:cache&lt;/code&gt; compiles your route definitions into a format that Laravel can load faster. If you have dozens or hundreds of routes, this makes a noticeable difference.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;php artisan view:cache&lt;/code&gt; pre-compiles your Blade templates into plain PHP. Normally, Laravel compiles Blade templates the first time they are requested. Caching them ahead of time means the first visitor to any page gets the same fast response as the thousandth visitor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These caches are rebuilt on every deployment so they always reflect your latest code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: The Symlink Swap (Zero-Downtime Magic)
&lt;/h2&gt;

&lt;p&gt;Remember how we said that Deploynix creates a new folder for each deployment? Here is why.&lt;/p&gt;

&lt;p&gt;Your web server (Nginx) is configured to serve your application from a specific path, something like &lt;code&gt;/home/deploynix/yoursite.com/current&lt;/code&gt;. But &lt;code&gt;current&lt;/code&gt; is not a regular folder. It is a symlink (short for symbolic link), which is essentially a shortcut that points to another folder.&lt;/p&gt;

&lt;p&gt;Right now, &lt;code&gt;current&lt;/code&gt; points to the folder containing your previous deployment's code. All of the steps we have discussed so far (downloading code, installing dependencies, running migrations) happened in a new folder. Your previous version of the application has been running the entire time, serving requests to your users without interruption.&lt;/p&gt;

&lt;p&gt;Once everything in the new folder is ready, Deploynix updates the &lt;code&gt;current&lt;/code&gt; symlink to point to the new folder. This change is atomic, meaning it happens in a single instant. One moment, Nginx is serving the old code. The next moment, it is serving the new code. There is no gap, no moment where the application is unavailable, no half-old-half-new state.&lt;/p&gt;

&lt;p&gt;This is what "zero-downtime deployment" means. Your users never see an error page or a loading spinner during deployment. They just seamlessly start seeing the new version of your application.&lt;/p&gt;

&lt;p&gt;The old deployment folders are kept around for a while in case you need to roll back. If something goes wrong with the new deployment, Deploynix can switch the symlink back to the previous folder in seconds, instantly reverting to the last working version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Restarting Workers and Services
&lt;/h2&gt;

&lt;p&gt;Some parts of your Laravel application run continuously in the background, not just when a user visits a page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Queue workers&lt;/strong&gt; process jobs that you have queued for background execution. Things like sending emails, processing uploaded images, or generating reports. These workers load your application code into memory when they start and keep running until they are told to stop.&lt;/p&gt;

&lt;p&gt;After a deployment, the queue workers are still running the old version of your code. Deploynix restarts them so they pick up the new code. This restart is done gracefully: the worker finishes processing its current job, then restarts with the new code. No jobs are lost or interrupted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron jobs&lt;/strong&gt; (scheduled tasks) are managed by Laravel's task scheduler. If you have scheduled commands defined in your application, they continue running on the server's schedule. The next time a scheduled task runs, it automatically uses the new code because it runs fresh through the &lt;code&gt;current&lt;/code&gt; symlink.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Your Application Is Live
&lt;/h2&gt;

&lt;p&gt;After all of these steps complete, your application is live and serving the latest version of your code. The entire process typically takes between 30 and 90 seconds, depending on the size of your application and the number of dependencies.&lt;/p&gt;

&lt;p&gt;Here is a summary of what happened:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your code was downloaded from GitHub to a new folder on the server.&lt;/li&gt;
&lt;li&gt;PHP dependencies were installed with Composer.&lt;/li&gt;
&lt;li&gt;Frontend assets were compiled with npm.&lt;/li&gt;
&lt;li&gt;The environment configuration was applied.&lt;/li&gt;
&lt;li&gt;New database migrations were run.&lt;/li&gt;
&lt;li&gt;Configuration, routes, and views were cached.&lt;/li&gt;
&lt;li&gt;The symlink was swapped to point to the new code.&lt;/li&gt;
&lt;li&gt;Background workers were restarted.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every subsequent deployment follows the same steps. The process is the same whether it is your second deployment or your two hundredth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When Something Goes Wrong
&lt;/h2&gt;

&lt;p&gt;Deployments can fail. A migration might have a bug. A Composer dependency might not install correctly. The build step might encounter an error. When this happens, Deploynix stops the deployment before the symlink swap.&lt;/p&gt;

&lt;p&gt;This is the beauty of the release-folder approach. If something fails during steps 1 through 6, the symlink never changes. Your previous deployment is still running, still serving users, completely unaffected. The failed deployment folder sits there harmlessly, and you can investigate what went wrong without any pressure because your application is still live.&lt;/p&gt;

&lt;p&gt;If a problem is discovered after the symlink swap (maybe a page throws an error that was not caught during testing), you can use Deploynix's rollback feature. Rolling back switches the symlink back to the previous deployment folder. It takes a few seconds, and your application is back to the last known good version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Updates
&lt;/h2&gt;

&lt;p&gt;After your first deployment, every subsequent deployment is the same process. You make changes to your code on your laptop, commit them to Git, push them to GitHub, and deploy through Deploynix. You can deploy manually by clicking the Deploy button in the dashboard, or you can set up automatic deployments that trigger whenever you push to a specific branch.&lt;/p&gt;

&lt;p&gt;Most teams set up automatic deployments for their main branch. The workflow becomes: write code, open a pull request, get it reviewed, merge it, and the deployment happens automatically. You can also schedule deployments for specific times using Deploynix's scheduled deployment feature, which is useful for changes that should go live during low-traffic hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Picture
&lt;/h2&gt;

&lt;p&gt;Deployment is the bridge between building your application and sharing it with the world. Understanding what happens during deployment makes you a more confident developer. When you know that the symlink swap is what prevents downtime, you can explain it to your team. When you know that migrations run in order, you can write them with confidence. When you know that the &lt;code&gt;.env&lt;/code&gt; file is separate from your code, you understand why it is safe to push your code to a public repository.&lt;/p&gt;

&lt;p&gt;Deploynix automates all of these steps so you can focus on building your application rather than managing servers. But automation works best when you understand what is being automated. Now you do. Go deploy something.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>deployment</category>
      <category>tutorial</category>
      <category>gettingstarted</category>
    </item>
    <item>
      <title>WebSockets 101 for Laravel Developers: From Concept to Production on Deploynix</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Wed, 22 Apr 2026 10:00:05 +0000</pubDate>
      <link>https://dev.to/deploynix/websockets-101-for-laravel-developers-from-concept-to-production-on-deploynix-15oo</link>
      <guid>https://dev.to/deploynix/websockets-101-for-laravel-developers-from-concept-to-production-on-deploynix-15oo</guid>
      <description>&lt;p&gt;Most web applications are built on a simple model: the browser sends a request, the server processes it, the server sends a response, and the connection closes. This request-response cycle has powered the web for decades, and it works perfectly for the vast majority of interactions. But there are moments when it falls short.&lt;/p&gt;

&lt;p&gt;Imagine a project management app where multiple team members are viewing the same task board. Alice moves a task from "In Progress" to "Done." Bob, who is looking at the same board, does not see the change until he refreshes the page. In a traditional HTTP application, Bob's browser has no way to know that something changed on the server. It would have to poll repeatedly, asking the server "did anything change?" every few seconds, which is wasteful and introduces latency.&lt;/p&gt;

&lt;p&gt;WebSockets solve this problem elegantly. Instead of closing the connection after each request-response cycle, a WebSocket connection stays open. The server can push data to the browser the instant something happens, without the browser asking. Alice moves the task, the server broadcasts the change, and Bob's browser updates in real time.&lt;/p&gt;

&lt;p&gt;This guide takes you from zero WebSocket knowledge to a working real-time feature in your Laravel application, deployed and running on Deploynix.&lt;/p&gt;

&lt;h2&gt;
  
  
  How WebSockets Work
&lt;/h2&gt;

&lt;p&gt;A WebSocket connection starts as a regular HTTP request. The browser sends an HTTP request with a special &lt;code&gt;Upgrade&lt;/code&gt; header that says "I would like to switch to the WebSocket protocol." If the server supports WebSockets, it responds with a 101 status code (Switching Protocols), and the connection transitions from HTTP to WebSocket.&lt;/p&gt;

&lt;p&gt;Once the connection is upgraded, it becomes a full-duplex communication channel. Both the browser and the server can send messages at any time, independently of each other. The connection remains open until either side explicitly closes it, or a network interruption occurs.&lt;/p&gt;

&lt;p&gt;The key differences from regular HTTP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistent: The connection stays open for the duration of the session. No repeated handshakes, no connection overhead for each message.&lt;/li&gt;
&lt;li&gt;Bidirectional: The server can push data to the client without the client requesting it. The client can also send data to the server at any time.&lt;/li&gt;
&lt;li&gt;Low latency: Messages are delivered in milliseconds because there is no connection setup for each message.&lt;/li&gt;
&lt;li&gt;Low overhead: WebSocket frames have minimal headers compared to HTTP requests, so the per-message overhead is tiny.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebSockets are ideal for features like live notifications, real-time dashboards, chat applications, collaborative editing, live activity feeds, and any situation where users need to see changes as they happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laravel Broadcasting: The Framework Layer
&lt;/h2&gt;

&lt;p&gt;Laravel does not ask you to work with raw WebSocket connections. Instead, it provides a Broadcasting system that abstracts the complexity into a clean, event-driven API. You define events in PHP, broadcast them, and listen for them in JavaScript. Laravel handles the plumbing.&lt;/p&gt;

&lt;p&gt;The broadcasting system has three components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events:&lt;/strong&gt; Standard Laravel events that implement the &lt;code&gt;ShouldBroadcast&lt;/code&gt; or &lt;code&gt;ShouldBroadcastNow&lt;/code&gt; interface. When these events are dispatched, Laravel serializes their public properties and sends them to the broadcasting driver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Channels:&lt;/strong&gt; Named channels that organize broadcasts. Laravel supports three types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public channels that anyone can listen to.&lt;/li&gt;
&lt;li&gt;Private channels that require authentication. The server verifies that the user is authorized to listen on the channel before allowing the connection.&lt;/li&gt;
&lt;li&gt;Presence channels that are private channels with the additional ability to see who else is listening. These are perfect for showing "who's online" indicators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Laravel Echo:&lt;/strong&gt; A JavaScript library that connects to the WebSocket server and provides a clean API for subscribing to channels and listening for events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverb vs. Third-Party Services
&lt;/h2&gt;

&lt;p&gt;Before Reverb, Laravel developers had two main options for WebSocket infrastructure: Pusher (a paid, hosted service) and laravel-websockets (a community package that ran a WebSocket server alongside your Laravel app). Each had trade-offs.&lt;/p&gt;

&lt;p&gt;Pusher is easy to set up but introduces a third-party dependency, ongoing costs, and the latency of routing messages through an external service. The laravel-websockets package kept everything in-house but was a community project with varying maintenance and could be tricky to run reliably in production.&lt;/p&gt;

&lt;p&gt;Laravel Reverb changed the equation. Reverb is a first-party Laravel package, maintained by the Laravel team, that provides a high-performance WebSocket server designed specifically for Laravel Broadcasting. It runs on your own servers, so there is no third-party dependency, no per-message pricing, and no added latency from routing through an external service.&lt;/p&gt;

&lt;p&gt;Reverb is built on ReactPHP and handles thousands of concurrent connections efficiently. It integrates seamlessly with Laravel's authentication system for private and presence channels, and it works out of the box with Laravel Echo.&lt;/p&gt;

&lt;p&gt;For most Laravel applications deploying on Deploynix, Reverb is the clear choice. You get full control over your WebSocket infrastructure, zero external dependencies, and first-party support from the Laravel team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Reverb in Your Laravel Application
&lt;/h2&gt;

&lt;p&gt;Let's walk through adding Reverb to an existing Laravel application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install Reverb
&lt;/h3&gt;

&lt;p&gt;Install Reverb using the Artisan installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan &lt;span class="nb"&gt;install&lt;/span&gt;:broadcasting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command installs the Reverb package, publishes the configuration file, adds the necessary environment variables to your &lt;code&gt;.env&lt;/code&gt; file, and installs the JavaScript dependencies (Laravel Echo and the Pusher JS client, which Echo uses as its WebSocket client library).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Configure Environment Variables
&lt;/h3&gt;

&lt;p&gt;After installation, your &lt;code&gt;.env&lt;/code&gt; file will have new Reverb-related variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BROADCAST_CONNECTION=reverb

REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=https
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In development, &lt;code&gt;localhost&lt;/code&gt; is fine. For production on Deploynix, you will configure these differently (we will cover that in the deployment section).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create a Broadcastable Event
&lt;/h3&gt;

&lt;p&gt;Create an event that implements &lt;code&gt;ShouldBroadcastNow&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>websockets</category>
      <category>reverb</category>
      <category>laravelecho</category>
    </item>
  </channel>
</rss>
