<?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: Daniel, Petrica Andrei-Daniel</title>
    <description>The latest articles on DEV Community by Daniel, Petrica Andrei-Daniel (@danielpetrica).</description>
    <link>https://dev.to/danielpetrica</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%2F254697%2F1f469a97-6b0b-46a6-a452-6e24c617843d.jpg</url>
      <title>DEV Community: Daniel, Petrica Andrei-Daniel</title>
      <link>https://dev.to/danielpetrica</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielpetrica"/>
    <language>en</language>
    <item>
      <title>Security Advisories, a Sync Gap, and Health Badges for Your README</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Mon, 29 Jun 2026 16:35:27 +0000</pubDate>
      <link>https://dev.to/danielpetrica/security-advisories-a-sync-gap-and-health-badges-for-your-readme-3j8a</link>
      <guid>https://dev.to/danielpetrica/security-advisories-a-sync-gap-and-health-badges-for-your-readme-3j8a</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F78en1z3mta66ohh0p4ol.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F78en1z3mta66ohh0p4ol.jpg" alt="Security Advisories, a Sync Gap, and Health Badges for Your README" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I shipped a new feature on LaraPlugins.io this week: security advisories for Laravel packages. They check plugin versions against known vulnerabilities.&lt;/p&gt;

&lt;p&gt;The very first thing they did? Surface a bug I had missed for two months.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;p&gt;A while back, I restructured the data sync pipeline. I disabled the old Packagist version sync commands and noted them in the code as "replaced." The replacement command, &lt;code&gt;app:plugin:health-sync-all&lt;/code&gt;, did sync health scores. But it did not sync plugin versions.&lt;/p&gt;

&lt;p&gt;Health scores, search, and everything else kept updating. So there was nothing visibly wrong. It was only when the new security advisory feature started cross-referencing version data that the stale state surfaced.&lt;/p&gt;

&lt;p&gt;When advisories first launched, they compared plugin versions against our local data, which was stale. This is what tipped me off. Some plugins were showing lower Health scores than they should because the version information was behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was fixed
&lt;/h2&gt;

&lt;p&gt;I re-enabled the daily version sync (top 5,000 packages) and the weekly full sync (all packages). The two-month backlog was significant. I switched from loading all records into memory to using cursor-based iteration with &lt;code&gt;chunk()&lt;/code&gt;, which cut peak memory usage substantially and lets the backlog process safely.&lt;/p&gt;

&lt;p&gt;Version sync is now also explicitly part of the weekly health pipeline too.&lt;/p&gt;

&lt;p&gt;For security advisories specifically, I added a fallback to the live Packagist API. Even if local version data is temporarily behind, advisory checks use the latest version info from Packagist directly. This means advisory badges stay accurate regardless of sync state. The gap was discovered because of the feature. The feature is now resilient to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  While I was in the pipeline anyway
&lt;/h2&gt;

&lt;p&gt;I also improved something I hope maintainers will find usefull: health badges for your README. They show your plugin's health score and security status at a glance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![LaraPlugins&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://laraplugins.io/badges/plugin/vendor/package.svg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://laraplugins.io/plugins/vendor/package)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;vendor/package&lt;/code&gt; with your Packagist package name or just use the get embed button on the plugin page on laraplugins.&lt;br&gt;&lt;br&gt;
The badge updates automatically with the new data on the site.&lt;/p&gt;

&lt;p&gt;Here is an example of the generated badges using spatie/laravel-medialibrary as an example. The Plugin fact checks is my favorite.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://laraplugins.io/plugins/spatie/laravel-medialibrary?ref=danielpetrica.com" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flaraplugins.io%2Fapi%2Finfocard%2Fspatie%2Flaravel-medialibrary%3Fdesign%3Dsimple%26theme%3Dlight" alt="Security Advisories, a Sync Gap, and Health Badges for Your README" width="480" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://laraplugins.io/plugins/spatie/laravel-medialibrary?ref=danielpetrica.com" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flaraplugins.io%2Fapi%2Finfocard%2Fspatie%2Flaravel-medialibrary%3Fdesign%3Dfacts%26theme%3Dlight" alt="Security Advisories, a Sync Gap, and Health Badges for Your README" width="380" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  If you use LaraPlugins to evaluate packages
&lt;/h2&gt;

&lt;p&gt;Your data is current now. Security advisory scores are backed by live version data. You can also pull advisories directly in your editor via the LaraPlugins MCP server without visiting the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;p&gt;The fix is deployed. The backlog of version data will take 4 to 8 hours to fully catch up. Everything should be current within the day.&lt;/p&gt;

&lt;p&gt;Transparency is part of building in public. Sharing what went wrong, what I fixed, and what I learned, that is the whole point i think.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thank you for reading my article
&lt;/h2&gt;

&lt;p&gt;I am working hard to build a health directory for laravel and any help is appreciated. If you want to help please subscribe to my blog.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                &amp;lt;span&amp;gt;Subscribe&amp;lt;/span&amp;gt;
                &amp;lt;span&amp;gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"&amp;gt;
    &amp;lt;g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round"&amp;gt;
        &amp;lt;g&amp;gt;
            &amp;lt;circle cx="4" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
            &amp;lt;circle cx="12" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
            &amp;lt;circle cx="20" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
        &amp;lt;/g&amp;gt;

    &amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;&amp;lt;/span&amp;gt;



            Email sent! Check your inbox to complete your signup.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;No spam. Unsubscribe anytime.&lt;/p&gt;

</description>
      <category>larapluginsio</category>
      <category>laravel</category>
      <category>personalprojects</category>
    </item>
    <item>
      <title>My LaraPlugins Performance Audit: 12 Fixes That Saved my Stressed server</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Mon, 22 Jun 2026 16:53:25 +0000</pubDate>
      <link>https://dev.to/danielpetrica/my-laraplugins-performance-audit-12-fixes-that-saved-my-stressed-server-1in1</link>
      <guid>https://dev.to/danielpetrica/my-laraplugins-performance-audit-12-fixes-that-saved-my-stressed-server-1in1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ajgwqqjlksbvoexhyda.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ajgwqqjlksbvoexhyda.jpg" alt="My LaraPlugins Performance Audit: 12 Fixes That Saved my Stressed server" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My 8 GB Hetzner VPS was sitting at 90%+ memory usage, all day, every day. New Relic alerts were pinging me constantly. The noise was so bad I started tuning them out, which is exactly when real problems slip through.&lt;/p&gt;

&lt;p&gt;LaraPlugins.io indexes over 76,000 Laravel packages. It runs health checks, syncs GitHub data, ingests Packagist versions, and serves a public directory. Behind the scenes, Horizon workers chew through jobs 24/7. A few weeks ago, the server was barely keeping up.&lt;/p&gt;

&lt;p&gt;I spent a week digging into the bottlenecks. Here are the 12 fixes that made a real difference, with the actual numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Starting Point
&lt;/h2&gt;

&lt;p&gt;Twelve Horizon supervisors, each with unlimited job processing. Workers grabbing 500 MB each. A monolithic maintenance supervisor that ran for two hours at a time. And during the weekly plugin health recalculation, a single job type was triggering &lt;strong&gt;50,000 Cloudflare cache purge jobs&lt;/strong&gt; in one cycle.&lt;/p&gt;

&lt;p&gt;The server had 8 GB of RAM. It was not enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: Split the Maintenance Monolith
&lt;/h2&gt;

&lt;p&gt;The maintenance supervisor was one worker processing everything: GitHub syncs, health calculations, analytics, and background tasks. It ran with a 7,200-second timeout. If anything stalled, everything stalled.&lt;/p&gt;

&lt;p&gt;I split it into dedicated supervisors: &lt;code&gt;github-sync&lt;/code&gt; (2 workers, 300s timeout, 400 MB) and &lt;code&gt;health&lt;/code&gt; (2 workers, 600s timeout, 500 MB). Each does one thing. If GitHub's API rate-limits the sync worker, the health worker keeps running.&lt;/p&gt;

&lt;p&gt;The old analytics supervisor became &lt;code&gt;background&lt;/code&gt;, merged with low-priority tasks, and dropped from 250 MB to 128 MB. Generic names like "low" and "analytics" became names that describe what the queue actually does. Makes debugging faster when something breaks at 3 AM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: Memory Caps Everywhere
&lt;/h2&gt;

&lt;p&gt;Every Horizon worker had &lt;code&gt;maxJobs: 0&lt;/code&gt;, meaning unlimited. A worker could process jobs forever, slowly leaking memory until the OOM killer stepped in.&lt;/p&gt;

&lt;p&gt;I set &lt;code&gt;maxJobs: 100&lt;/code&gt; across all supervisors. After 100 jobs, the worker restarts. Memory gets released back to the OS. The defaults also dropped: 500 MB became 256 MB on the default queue, 384 MB on packagist-high, and 128 MB on background. Combined, the memory ceiling dropped by roughly 2 GB across all workers.&lt;/p&gt;

&lt;p&gt;The failed job retention window went from 10,080 minutes (7 days) to 7,080 (5 days). Horizon's queue trim snapshot interval jumped from 12 to 2,016 minutes. These are small config changes that add up to less Redis memory churn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3: The 50,000-Job Problem
&lt;/h2&gt;

&lt;p&gt;Every time a plugin's data was updated, a job dispatched to purge its Cloudflare cache. During the weekly health recalculation, with all 56,000 plugins being refreshed, &lt;strong&gt;over 50,000 &lt;code&gt;InvalidatePluginCloudflareCache&lt;/code&gt; jobs&lt;/strong&gt; hit the queue.&lt;/p&gt;

&lt;p&gt;That is 50,000 individual Redis pushes, 50,000 worker pickups, 50,000 Cloudflare API calls spaced across the queue. The queue backed up. Workers spent cycles dispatching and picking up purge jobs instead of doing actual health calculations.&lt;/p&gt;

&lt;p&gt;The fix: a Redis &lt;code&gt;SADD&lt;/code&gt; collects purged paths into a set. A single &lt;code&gt;FlushPendingCloudflarePurgesJob&lt;/code&gt; pops 30 URLs at a time, sends them to Cloudflare's bulk purge endpoint, and self-reschedules if more remain. Max 10 batches per cycle, or 300 URLs per flush. Instead of 50,000 jobs, it is one job calling itself. The Redis set acts as a natural deduplicator as well: if the same plugin path is marked for purge twice, &lt;code&gt;SADD&lt;/code&gt; only stores it once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 4: The Column Nobody Reads
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;plugins&lt;/code&gt; table has a &lt;code&gt;packagist_metadata&lt;/code&gt; column. It is a JSON blob containing every version's metadata: requirements, dependencies, descriptions. It is large. And it was loaded on &lt;em&gt;every single query&lt;/em&gt; to the Plugin model, even though sync jobs never read it. They only write it.&lt;/p&gt;

&lt;p&gt;I added a &lt;code&gt;WithoutPackagistMetadataScope&lt;/code&gt; global scope to the Plugin model. Every query now excludes that column by default. Jobs that need the metadata load it explicitly with &lt;code&gt;withoutGlobalScope()&lt;/code&gt;. The query payload for list operations dropped significantly. MySQL spent less time shuffling JSON blobs across the wire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 5: Indexes That Should Have Been There
&lt;/h2&gt;

&lt;p&gt;Three missing indexes: &lt;code&gt;plugin_versions&lt;/code&gt; support version columns (used in every compatibility check), &lt;code&gt;failed_jobs.queue&lt;/code&gt; (Horizon's dashboard queries it constantly), and the vendor detail page query path. These were not exotic compound indexes. They were obvious single-column indexes that I simply had not added when the tables were created.&lt;/p&gt;

&lt;p&gt;The vendor detail page also got a column selection pass: instead of &lt;code&gt;SELECT *&lt;/code&gt;, it now selects only the columns it actually renders. Small change, but the page went from noticeably slow to instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 6: Cache Longer
&lt;/h2&gt;

&lt;p&gt;Aggregate queries (plugin counts by tag, health score distributions, PHP version stats) were cached for too short a window. I bumped the TTLs to 6 hours and 24 hours depending on the query. These numbers do not change between deploys. There is no reason to recalculate them every 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 7: Queue MCP analytics Off the Critical Path
&lt;/h2&gt;

&lt;p&gt;LaraPlugins exposes an MCP server for the plugin search tool. Every request was logging anonymized analytics events synchronously before returning a response. A event write meant a slow MCP response. Moved the logging to a queued job on the background queue. MCP responses stayed fast regardless of log throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 8: Health Checks That Checked Nothing
&lt;/h2&gt;

&lt;p&gt;Several health check jobs were running at frequencies that did not match the data freshness they protected. Some were redundant checks that another job already performed. Trimmed the schedule to match actual refresh needs and removed the duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 9: MySQL Tuning
&lt;/h2&gt;

&lt;p&gt;Increased the &lt;code&gt;sort_buffer_size&lt;/code&gt; in the Docker Compose MySQL config. LaraPlugins runs sorting-heavy queries on plugin lists (by downloads, by stars, by health score). The default sort buffer was too small for the dataset size, causing disk-based sorts that slowed down listing pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 10: Horizon Retention Trimming
&lt;/h2&gt;

&lt;p&gt;Horizon stores job history in Redis. The completed job retention was at 60 minutes, the queue trim snapshot interval at 12 minutes. These are fine for a small app. For LaraPlugins, with thousands of jobs per hour, Redis memory was bleeding into job history storage. Bumped the queue trim to 2,016 minutes and reduced failed job retention to 5 days. Redis memory stabilized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 11: Rate Limit Recovery
&lt;/h2&gt;

&lt;p&gt;GitHub's API rate-limits aggressively. When the sync worker hit the limit, it would crash and Horizon would retry it, sometimes before the rate limit window reset. Added explicit rate limit detection with proper backoff: when GitHub returns a 429 or a retry-after header, the job releases itself back to the queue with a delay matching the reset window. No more spinning wheels against a rate limit wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 12: Nightwatch Silencing
&lt;/h2&gt;

&lt;p&gt;Laravel Nightwatch was ingesting every queue job, every request, every log into its agent. For a job-heavy app like LaraPlugins, that meant an enormous volume of observability data, most of it noise. Reduced the event ingestion to only what I actually look at. The agent container stopped being a resource drain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;The 8 GB VPS dropped from 90%+ memory usage with constant alerts to a stable 65 to 80% range. New Relic went quiet. Response times stayed flat at under 100 ms average. No regressions.&lt;/p&gt;

&lt;p&gt;The Horizon queue shrank from 12 supervisors doing vague things to 9 supervisors doing specific things. Memory leaks from unlimited job processing stopped. The 50,000-job Cloudflare purge spike became a single self-rescheduling job.&lt;/p&gt;

&lt;p&gt;And I can sleep without my phone buzzing about a server that is "probably fine."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;Most of these fixes were not clever. They were cleanup. Indexes I forgot to add. A column nobody read being loaded on every query. A cache TTL that was too short for no good reason. Queue names that made debugging harder.&lt;/p&gt;

&lt;p&gt;If I were starting LaraPlugins again, I would add &lt;code&gt;maxJobs&lt;/code&gt; to every Horizon supervisor from day one. I would default to global scopes for large JSON columns on read-heavy tables. And I would batch external API calls by default instead of dispatching one job per call. These are not architecture decisions. They are habits.&lt;/p&gt;

&lt;p&gt;The biggest lesson: when your monitoring goes noisy, you have exactly two choices. Fix the root cause, or tune the alerts. Every day you tune the alerts without fixing the cause, you are training yourself to ignore your own dashboard.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>larapluginsio</category>
      <category>personalprojects</category>
      <category>guides</category>
    </item>
    <item>
      <title>How to Replace Raw S3 URLs with a Laravel Image Proxy (and Keep Your CDN Cache)</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Mon, 15 Jun 2026 15:52:36 +0000</pubDate>
      <link>https://dev.to/danielpetrica/how-to-replace-raw-s3-urls-with-a-laravel-image-proxy-and-keep-your-cdn-cache-113n</link>
      <guid>https://dev.to/danielpetrica/how-to-replace-raw-s3-urls-with-a-laravel-image-proxy-and-keep-your-cdn-cache-113n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7caky1lflj9owxohkez.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7caky1lflj9owxohkez.jpg" alt="How to Replace Raw S3 URLs with a Laravel Image Proxy (and Keep Your CDN Cache)" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're serving images and media files directly from an S3-compatible object storage (like Hetzner Object Storage, AWS S3, or DigitalOcean Spaces), you're probably used to URLs that look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://storage.example.com/my-bucket/project/files/posts/hello-world/cover.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's ugly. It exposes your storage provider and bucket name. And if your bucket is private, you can't even serve those URLs directly. You'd need signed URLs or a proxy.&lt;/p&gt;

&lt;p&gt;Here's how I replaced all raw S3 media URLs on my Laravel blog with clean proxied paths through the application, while keeping 24-hour CDN caching for performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;We run a typical Laravel + Octane stack behind Traefik with Cloudflare. All media is stored on an S3-compatible object storage in two buckets: a private one for uploaded media (&lt;code&gt;project/files&lt;/code&gt;) and a public one for auto-generated Open Graph images (&lt;code&gt;project/og-images&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Originally, the app generated URLs directly to object storage using &lt;code&gt;Storage::url()&lt;/code&gt;. This meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Private bucket URLs were inaccessible (and switching to public was not an option)&lt;/li&gt;
&lt;li&gt;Provider details were exposed in every page's HTML&lt;/li&gt;
&lt;li&gt;No control over caching at the application level&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Build a URL helper
&lt;/h2&gt;

&lt;p&gt;First, I created a &lt;code&gt;MediaUrlBusiness&lt;/code&gt; class that centralizes URL generation for both disks. Instead of calling &lt;code&gt;Storage::disk('og-images')-&amp;gt;url($path)&lt;/code&gt; or &lt;code&gt;Storage::url($path)&lt;/code&gt; in blade templates, I now call:&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;MediaUrlBusiness&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forOgImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → /storage/og-images/posts/slug.png&lt;/span&gt;
&lt;span class="nc"&gt;MediaUrlBusiness&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → /storage/media/posts/slug/cover.png&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a clean relative path like &lt;code&gt;/storage/og-images/...&lt;/code&gt;. All 6+ blade templates that previously generated raw S3 URLs now use this single helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: The proxy controller
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ObjectProxyController&lt;/code&gt; catches all &lt;code&gt;/storage/{disk}/{path}&lt;/code&gt; requests. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validates the disk is one of the allowed ones (&lt;code&gt;media&lt;/code&gt; → private bucket, &lt;code&gt;og-images&lt;/code&gt; → public bucket)&lt;/li&gt;
&lt;li&gt;Resolves the full S3 key using the disk's configured prefix&lt;/li&gt;
&lt;li&gt;Streams the file from object storage using &lt;code&gt;response()-&amp;gt;stream()&lt;/code&gt; with proper &lt;code&gt;Content-Type&lt;/code&gt; and &lt;code&gt;Content-Length&lt;/code&gt; headers&lt;/li&gt;
&lt;li&gt;Sets a &lt;code&gt;Cache-Control: public, max-age=86400&lt;/code&gt; header so Cloudflare and browsers cache aggressively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the core of it:&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;$disk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$diskMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$diskSegment&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$disk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;readStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fullPath&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;response&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;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&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="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;fpassthru&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nb"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$disk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fullPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'Content-Length'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$disk&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fullPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'Cache-Control'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'public, max-age=86400, immutable'&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;A detail that tripped me up: &lt;code&gt;Storage::download()&lt;/code&gt; and &lt;code&gt;streamDownload()&lt;/code&gt; buffer the entire file into memory. For large images, that wastes memory. Switching to &lt;code&gt;readStream() + response()-&amp;gt;stream()&lt;/code&gt; sends the file directly from S3 to the client without buffering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Laravel's S3 driver supports &lt;code&gt;fileSize()&lt;/code&gt;. For custom disks, use &lt;code&gt;$disk-&amp;gt;size()&lt;/code&gt; instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important: anything in the private bucket is now accessible through the proxy if someone guesses the path. Make sure that bucket only holds public-facing assets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 3: Route and middleware setup
&lt;/h2&gt;

&lt;p&gt;The proxy route lives in &lt;code&gt;routes/static.php&lt;/code&gt; alongside other cacheable frontend routes:&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="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'/storage/{disk}/{path}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectProxyController&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="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;'path'&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'storage.proxy'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also modified the &lt;code&gt;SetCacheControlHeader&lt;/code&gt; middleware to skip responses that already have a &lt;code&gt;Cache-Control&lt;/code&gt; header set (since the proxy controller sets its own). This way the middleware doesn't override our carefully tuned 24-hour cache policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Wire it up everywhere
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MediaUrlBusiness&lt;/code&gt; helper is now used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Post, page, and tag show views, for both OG images and feature images&lt;/li&gt;
&lt;li&gt;Homepage and archive listings, for post card images&lt;/li&gt;
&lt;li&gt;Case study components, for service images&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OgImageBusiness&lt;/code&gt;, for storing and retrieving generated OG images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every call to &lt;code&gt;Storage::disk('og-images')-&amp;gt;url(...)&lt;/code&gt; or &lt;code&gt;Storage::url(...)&lt;/code&gt; was replaced with the appropriate &lt;code&gt;MediaUrlBusiness::for*()&lt;/code&gt; method.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about the admin panel?
&lt;/h2&gt;

&lt;p&gt;The Filament admin panel has its own media proxy. The existing &lt;code&gt;MediaProxyController&lt;/code&gt; handles CORS issues when editors browse and insert images in the RichEditor. That stays as-is; it's authenticated and not meant for public caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://storage.example.com/my-bucket/project/files/posts/hello-world/cover.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/storage/media/posts/hello-world/cover.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, provider-agnostic, and cache-friendly. The URL no longer leaks my storage provider or bucket name. And if I ever switch providers, I only need to change the disk configuration. The public URLs stay the same.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Need this for your site? If your team doesn't have the bandwidth to implement this, I can set it up for you: clean proxied image URLs, CDN caching, and private bucket security.&lt;/em&gt; &lt;a href="https://danielpetrica.com/work-with-me" rel="noopener noreferrer"&gt;&lt;em&gt;Get in touch →&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>devops</category>
      <category>objectstorage</category>
    </item>
    <item>
      <title>50,000 Spam Emails and a 3 AM Panic: What Happened When I Forgot About a Side Project</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Mon, 25 May 2026 14:28:57 +0000</pubDate>
      <link>https://dev.to/danielpetrica/50000-spam-emails-and-a-3-am-panic-what-happened-when-i-forgot-about-a-side-project-5190</link>
      <guid>https://dev.to/danielpetrica/50000-spam-emails-and-a-3-am-panic-what-happened-when-i-forgot-about-a-side-project-5190</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33h3if0mhtkabfevhgb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33h3if0mhtkabfevhgb0.png" alt="50,000 Spam Emails and a 3 AM Panic: What Happened When I Forgot About a Side Project" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short version:&lt;/strong&gt; Someone exploited an outdated Livewire version on a dormant side project of mine. They stole my &lt;code&gt;.env&lt;/code&gt; file, used the Mailcoach API keys to send 50,000 spam emails, and I spent a panicked night tearing through every project I own trying to find the source.&lt;/p&gt;

&lt;p&gt;Here is the full story, what I learned, and why Docker saved me from a much worse outcome.&lt;/p&gt;

&lt;p&gt;It was Wednesday, May 20. I was in class when my phone buzzed with an email from Mailcoach:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Your submissions have been blocked due to a high bounce rate or spam reports.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I read it. Felt that cold drop in my stomach. And then I had to put my phone away and finish class.&lt;/p&gt;

&lt;p&gt;There is a special kind of dread that comes from knowing something is wrong but not being able to look at it. I sat through the rest of my Japanese lesson with half my brain running through worst-case scenarios.&lt;/p&gt;

&lt;p&gt;Around 18:00, I finally opened my laptop.&lt;/p&gt;

&lt;h3&gt;
  
  
  The moment of terror
&lt;/h3&gt;

&lt;p&gt;I logged into Mailcoach and my heart stopped. Over 50,000 emails had been sent from my account. All of them spam. Phishing links. Going out under my name.&lt;/p&gt;

&lt;p&gt;I had never experienced anything like this before. I was shocked. My immediate reaction was to delete every single API token in Mailcoach and my GitHub API tokens. Then I wrote to their support team asking for help figuring out how this happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  The long night
&lt;/h3&gt;

&lt;p&gt;I spent the next nine hours reviewing every project I own.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;danielpetrica.com&lt;/code&gt;. &lt;code&gt;laraplugins.io&lt;/code&gt;. &lt;code&gt;adventcalendar.tech&lt;/code&gt;. And some other smaller, unused projects. Every small demo site, every experiment, every dormant project sitting on my server. I checked logs, checked file permissions, checked for anything unusual. Nothing.&lt;/p&gt;

&lt;p&gt;To make things worse, that same day Symfony announced 19 CVEs across their projects. My brain went there immediately. Was it Symfony? Was one of my sites running an affected version? I went down that rabbit hole for a while.&lt;/p&gt;

&lt;p&gt;Nothing.&lt;/p&gt;

&lt;p&gt;At 3 AM, I had to make a call. I secured what I could, changed what I could, and went to sleep. I was frustrated. I was tired. And I still had no idea what happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding the source
&lt;/h3&gt;

&lt;p&gt;The next day I kept digging. Project by project. And then I found it.&lt;/p&gt;

&lt;p&gt;A small side project. One of those things you build, launch, and forget about. It was running an outdated version of Livewire with a public CVE. I had not updated it. That was my fault.&lt;/p&gt;

&lt;p&gt;The attacker found it through automated scanning. These bots crawl the web looking for known vulnerabilities in exposed applications. They found Livewire, found the CVE, and were inside within hours. They uploaded a web admin script that let them read anything the Apache user had access to inside that Docker container.&lt;/p&gt;

&lt;p&gt;Including my &lt;code&gt;.env&lt;/code&gt; file. Which held my Mailcoach API keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Docker blessing
&lt;/h3&gt;

&lt;p&gt;Here is where things could have been much worse.&lt;/p&gt;

&lt;p&gt;That side project was running inside a Docker container. The attacker had access to that container, but they could not escape it. They could not reach my other projects, my other databases, or the host system itself. The blast radius was limited to that one service.&lt;/p&gt;

&lt;p&gt;I originally set up Docker Compose just as a convenience for myself. I never thought of it as a security measure in the beginning. But it ended up being the wall that stopped the attack from spreading.&lt;/p&gt;

&lt;h3&gt;
  
  
  The evidence I lost
&lt;/h3&gt;

&lt;p&gt;This part still stings.&lt;/p&gt;

&lt;p&gt;When I finally identified the compromised container, I did the natural thing. I took it down: &lt;code&gt;docker compose down -v&lt;/code&gt;. Volumes included.&lt;/p&gt;

&lt;p&gt;And just like that, the attacker's files were gone. The web shell, the logs of what they did, any clues about their entry point,all of it erased.&lt;/p&gt;

&lt;p&gt;I realized my mistake later. I should have copied the container's storage before killing it. I should have preserved the evidence. But in the moment, my only thought was, "Shut it down now."&lt;/p&gt;

&lt;p&gt;I spent the next few hours going through my reverse proxy logs instead. Those survived. They showed the attacker's pattern clearly. Automated scanners hit the site repeatedly. Once they confirmed the Livewire vulnerability, the actual compromise happened within hours. This was not a targeted attack on me. I was just another domain on a list.&lt;/p&gt;

&lt;p&gt;What scared me most was how fast everything happened. The scan, the exploit, the credential harvest, the spam campaign. It felt automated end-to-end. If AI is making these attacks faster and more efficient, we are all going to need to be much more proactive about monitoring and maintenance. Myself included.&lt;/p&gt;

&lt;h3&gt;
  
  
  The silver lining
&lt;/h3&gt;

&lt;p&gt;Mailcoach handled this better than I could have hoped.&lt;/p&gt;

&lt;p&gt;They noticed the spam spike before I did. They suspended my submissions proactively. When I reached out, they worked with me to investigate. And when the dust settled, they dropped the charges for those 50,000+ emails from my invoice entirely.&lt;/p&gt;

&lt;p&gt;That is the kind of provider behavior that builds loyalty for life.&lt;/p&gt;

&lt;p&gt;Thank you to the Mailcoach team.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I changed
&lt;/h3&gt;

&lt;p&gt;Since this happened, I have made a few changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I set up automated dependency update alerts for every project, including the dormant ones. If a project has a &lt;code&gt;composer.json&lt;/code&gt;, I get notified when its dependencies are outdated.&lt;/li&gt;
&lt;li&gt;I conducted a review of all my side projects. Some got updated. Some got archived. Some got shut down entirely.&lt;/li&gt;
&lt;li&gt;I reviewed my API token strategy. Tokens now have strict scopes and shorter lifespans where possible.&lt;/li&gt;
&lt;li&gt;I am also looking into environment variable management strategies and exploring how to better align them with my Docker setup.&lt;/li&gt;
&lt;li&gt;I accepted that I need better monitoring. A 15:52 alert that I cannot act on until 18:00 is useless if I do not have a plan for that gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The uncomfortable truth
&lt;/h3&gt;

&lt;p&gt;This happened because I made a mistake. I had a public, dormant website with an outdated dependency. I knew better; I just did not prioritize it until I forgot about the site entirely.&lt;/p&gt;

&lt;p&gt;But the takeaway is not just "be more careful." It is that our industry has a long tail of forgotten projects that are ticking time bombs. Every developer I know has side projects gathering dust on a server somewhere. Those are exactly what the scanners are looking for.&lt;/p&gt;

&lt;p&gt;Go update your dependencies. Or shut down what you are not maintaining. Because the bots are not going to wait until you have time. I learned that the hard way.&lt;/p&gt;

&lt;p&gt;Have fun building. And update your Livewire, please.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; I was already convinced of this before, but now I am certain: Mailcoach is the best email tool for campaigns and transactional emails. Go check them out &lt;a href="https://www.mailcoach.app/solutions/solutions-for-developers/?via=danielBlogIncident&amp;amp;ref=danielpetrica.com" rel="noopener noreferrer"&gt;here (referral link)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>personalprojects</category>
      <category>news</category>
      <category>ghost</category>
      <category>safety</category>
    </item>
    <item>
      <title>The State of Laravel Packages 2026</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Sat, 31 Jan 2026 23:00:00 +0000</pubDate>
      <link>https://dev.to/danielpetrica/the-state-of-laravel-packages-2026-35ce</link>
      <guid>https://dev.to/danielpetrica/the-state-of-laravel-packages-2026-35ce</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s220a6anl8v987yzzf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s220a6anl8v987yzzf9.png" alt="The State of Laravel Packages 2026" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the end of November, I shared a questionnaire to discover what the community thinks about the state of Laravel packages today. I wanted to focus on the real-world pain points developers face. To ensure I got busy developers to respond, I kept it short, under 3 minutes on average. &lt;/p&gt;

&lt;p&gt;From this findings &lt;a href="https://laraplugins.io/?ref=danielpetrica.com" rel="noopener noreferrer"&gt;Laraplugins.io&lt;/a&gt; was born. Now it indexes more than 16000 plugins. Go find their health status now&lt;/p&gt;

&lt;p&gt;Without an established audience, I managed to reach around 200 responses.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Thank you to everyone who participated or shared it; the insights gathered were eye-opening.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article illustrates what the community had to say. &lt;/p&gt;

&lt;h2&gt;
  
  
  Get my updates in your &lt;strong&gt;inbox&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Register to be the first to receive my new articles on Laravel, DevOps, my projects, and the future of the Laravel package ecosystem.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                &amp;lt;span&amp;gt;Subscribe&amp;lt;/span&amp;gt;
                &amp;lt;span&amp;gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"&amp;gt;
    &amp;lt;g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round"&amp;gt;
        &amp;lt;g&amp;gt;
            &amp;lt;circle cx="4" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
            &amp;lt;circle cx="12" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
            &amp;lt;circle cx="20" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
        &amp;lt;/g&amp;gt;

    &amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;&amp;lt;/span&amp;gt;



            Email sent! Check your inbox to complete your signup.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;No spam. Unsubscribe anytime. Thank You.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who responded?
&lt;/h3&gt;

&lt;p&gt;To ensure the data reflected active ecosystem users, I first asked how often respondents develop new features or applications using Laravel. &lt;/p&gt;

&lt;p&gt;82% develop new Laravel features &lt;strong&gt;Daily or Weekly&lt;/strong&gt;. That number grows to &lt;strong&gt;94%&lt;/strong&gt; if we include monthly respondents. This tells me the majority of you aren't just maintaining legacy code; you're actively choosing Laravel for new work. That's awesome to see. &lt;/p&gt;

&lt;p&gt;(Going forward, the data below focuses on this active daily/weekly/monthly cohort to ensure relevance.) &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkkgh0udz8hy8u70j350m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkkgh0udz8hy8u70j350m.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How often do you develop new features or applications using Laravel?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Build vs. Buy" Mentality
&lt;/h3&gt;

&lt;p&gt;When needing standard functionality (e.g., SEO, Media handling, Payments), what is the default reaction? Do developers reinvent the wheel or look for existing solutions? &lt;/p&gt;

&lt;p&gt;The results show a strong preference for existing solutions. &lt;strong&gt;96%&lt;/strong&gt; of respondents indicated they either immediately look for existing packages/SDKs (65%) or search for packages first before potentially deciding to build it themselves (31%). &lt;/p&gt;

&lt;p&gt;This confirms that Laravel developers prefer not to rebuild standard functionality unless forced to. Remember that detail for later. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyc0nfu6pxetkdi05ynn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyc0nfu6pxetkdi05ynn6.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;When you need standard functionality what is your default reaction?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Where do developers find packages?
&lt;/h3&gt;

&lt;p&gt;I was curious if my personal habit of just Googling everything was normal. It turns out, it is. (Note: Respondents could select multiple options). &lt;/p&gt;

&lt;p&gt;Unsurprisingly, &lt;strong&gt;Web Search (74%)&lt;/strong&gt; dominates. However, it is closely followed by &lt;strong&gt;Packagist (51%)&lt;/strong&gt; and community news sources like &lt;strong&gt;Laravel News or newsletters (49%)&lt;/strong&gt;. Interestingly, developers seem to trust curated news sources and general web results significantly more than &lt;strong&gt;GitHub search (42%)&lt;/strong&gt; or social media feeds &lt;strong&gt;(19%)&lt;/strong&gt; when looking for tools. So, a bit surprisingly, this means we choose to trust other developers' recommendations via news sites more than random posts on social media. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle8m8cnwampdqkp1m8it.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle8m8cnwampdqkp1m8it.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Where do you primarily go to find Laravel packages today?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How heavy are our composer.json files?
&lt;/h3&gt;

&lt;p&gt;I asked how many third-party packages (excluding default Laravel dependencies) are in an average project to gauge the "baggage" we're willing to carry. &lt;/p&gt;

&lt;p&gt;Over half of the respondents &lt;strong&gt;(54%)&lt;/strong&gt; fall into the &lt;strong&gt;6–15 package&lt;/strong&gt; range. Given standard best practices today (involving tools like PHPStan, Pint, Debugbar, or Telescope), this feels normal. &lt;/p&gt;

&lt;p&gt;I was surprised that &lt;strong&gt;38%&lt;/strong&gt; managed to keep projects between &lt;strong&gt;0 and 5 packages,&lt;/strong&gt; that is impressive discipline! Honestly, how do you manage to keep it so low? Please send me a DM, I'm very curious. Finally, &lt;strong&gt;7%&lt;/strong&gt; have &lt;strong&gt;16 or more packages&lt;/strong&gt;. That seems heavy, how long does your composer update take at that point? Luckily Composer is fast, but still. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rsnc7oei4tkylti7p9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rsnc7oei4tkylti7p9w.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Roughly how many third-party packages are in your average project?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vetting Process
&lt;/h3&gt;

&lt;p&gt;How much time do you spend researching before typing composer require? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;61%&lt;/strong&gt; spend between &lt;strong&gt;5 and 30 minutes&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&amp;lt;5 minutes&lt;/strong&gt; was the next largest group with 19%
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30%&lt;/strong&gt; spend more than &lt;strong&gt;30 minutes&lt;/strong&gt; doing a deeper dive. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This suggests that for the majority, the criteria chosen for selection needs to be scannable quickly, things like update recency, documentation quality, and popularity, rather than deep code audits. We have what Italians call "&lt;em&gt;grilletto facile&lt;/em&gt;" (a quick trigger) for packages that look good on the surface. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pibwzbuqqb5y6idz11z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pibwzbuqqb5y6idz11z.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;How much time do you typically spend researching and auditing a package&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When asked to rank the most important factors when choosing between similar packages, the results confirmed this: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recency of updates / Maintenance status&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation quality&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Stars / Popularity&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author reputation&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The quality and frequency of updates are clearly king. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forogqr5t5rvied6o33a5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forogqr5t5rvied6o33a5.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;What is the most important factor when choosing between two similar packages?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A related note on compatibility:&lt;/strong&gt; I also asked if you verify if a package supports your specific tech stack (e.g., Blade vs. Vue/React) before installing. &lt;strong&gt;56%&lt;/strong&gt; said yes, as compatibility is often an issue. &lt;strong&gt;32%&lt;/strong&gt; mainly use headless packages, and &lt;strong&gt;11%&lt;/strong&gt; don't check too much, planning to adapt the code anyway. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhj8hg3w78mbk4ci7pse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhj8hg3w78mbk4ci7pse.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Do you verify if a package supports the specific tech stack you use before installing?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Biggest Frustrations
&lt;/h3&gt;

&lt;p&gt;This was the most critical part of the survey: what makes developers unsatisfied with the current ecosystem? &lt;/p&gt;

&lt;p&gt;It is a statistical tie for the top frustration. &lt;strong&gt;70%&lt;/strong&gt; cited &lt;strong&gt;lack of documentation or usage examples&lt;/strong&gt; , while virtually the same amount cited the issue of &lt;strong&gt;too many abandoned or outdated packages&lt;/strong&gt; cluttering search results. &lt;/p&gt;

&lt;p&gt;We have all experienced searching for functionality and finding five packages that haven't been updated since Laravel 8.  &lt;/p&gt;

&lt;p&gt;In third place &lt;strong&gt;(28%)&lt;/strong&gt; was the difficulty of comparing features between similar packages. And finally, not knowing if a package is compatible with my Laravel version represents &lt;strong&gt;13%&lt;/strong&gt; of results.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foeullzkcnwvq101qtxzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foeullzkcnwvq101qtxzv.png" alt="The State of Laravel Packages 2026" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;What is your biggest frustration when searching for a package?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Community Wish-list
&lt;/h3&gt;

&lt;p&gt;I finally asked an open-ended question: &lt;em&gt;"If you could wave a magic wand and improve one thing about the Laravel ecosystem today, what would it be?"&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;I received 32 wonderful, detailed responses. While some covered broader topics, the vast majority focused specifically on the pain of managing third-party packages. Here are some representative voices from the community: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the broader community:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"...less expensive Laracons..."&lt;/em&gt; . I can understand this! Have you heard of L&lt;a href="https://laravellive.jp/en?utm_source=danielpetrica_com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=pluginreport" rel="noopener noreferrer"&gt;aravelLive.jp&lt;/a&gt;? The price is very low and japan is japan, we could even meet for a coffee there. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"More popularity with employers"&lt;/em&gt; . We all want this. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Laravel patterns often don't scale well. More education around team-oriented patterns..."&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"More focus on plain Laravel + Blade"&lt;/em&gt; and &lt;em&gt;"I want to make it more type safe..."&lt;/em&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On packages specifically (this is where it got really interesting):&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"Making keeping packages up to date for each Laravel version the norm. Many packages are abandoned after a few versions."&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Standardize package info presentation to include age of project, last update, version compatibility, preq, open issues."&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Less fragmentation of third party packages would be nice."&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Reduce the number of duplicate packages that do the same or similar functions."&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"A Laravel centric site with packages including their usages... a dedicated site would be better."&lt;/em&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also saw some direct debate in the responses, like requests for &lt;em&gt;"More livewire support, fewer paid libraries"&lt;/em&gt; sitting right next to a simple &lt;em&gt;"get rid of livewire"&lt;/em&gt;. The community has strong and mixed feelings! &lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The data paints a clear picture: Laravel developers want to use packages, but the friction involved in finding reliable, well-documented, and up-to-date tools is a major pain point. The ecosystem is vibrant, but that vibrancy creates noise that is hard to filter. &lt;/p&gt;

&lt;p&gt;The recurring theme in your open-ended responses was a desire for a centralized, standardized way to evaluate packages specifically for Laravel. Those suggestions literally gave me an idea. &lt;/p&gt;

&lt;p&gt;Inspired by this data and your specific feedback, I started working on &lt;a href="https://laraplugins.io/?utm_source=danielpetrica_com&amp;amp;utm_medium=blog&amp;amp;utm_campaign=pluginreport" rel="noopener noreferrer"&gt;&lt;strong&gt;LaraPlugins.io&lt;/strong&gt;&lt;/a&gt; as an attempt to address these frustrations. It’s a new directory focused on helping developers find, compare, and check maintenance status for Laravel-specific packages. It can even be connected to your ai agent with a simple fast mcp connection now! See how here &lt;a href="https://laraplugins.io/mcp?ref=danielpetrica.com" rel="noopener noreferrer"&gt;https://laraplugins.io/mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The beta of the site is up but with an initial health score algorithm.&lt;br&gt;&lt;br&gt;
I don't pretend to have the perfect way to judge article healthy so please give the site a try then send me a feedback on the site health score system.&lt;br&gt;&lt;br&gt;
Only a collective effort can produce something really useful to all of us.&lt;br&gt;&lt;br&gt;
in the incoming AI development having a trustworthy directory would make AI agents able to pick healthy packages for the development project.&lt;/p&gt;

&lt;p&gt;P.S. Laravel is a trademark of Taylor Otwell. LaraPlugins.io is an independent, community-driven directory and is not officially affiliated with, endorsed by, or sponsored by Laravel or Taylor Otwell. Trademark usage is intended solely to identify the Laravel framework and its ecosystem packages.​&lt;/p&gt;

</description>
      <category>larapluginsio</category>
      <category>report</category>
      <category>laravel</category>
      <category>news</category>
    </item>
    <item>
      <title>LaraPlugins Devlog: Sub-Second Search, Vendor Profiles, and Building an AI Brain (MCP)</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Sun, 18 Jan 2026 18:44:26 +0000</pubDate>
      <link>https://dev.to/danielpetrica/laraplugins-devlog-sub-second-search-vendor-profiles-and-building-an-ai-brain-mcp-5ddp</link>
      <guid>https://dev.to/danielpetrica/laraplugins-devlog-sub-second-search-vendor-profiles-and-building-an-ai-brain-mcp-5ddp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg6bgconm8svvdnd1ogt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg6bgconm8svvdnd1ogt.png" alt="LaraPlugins Devlog: Sub-Second Search, Vendor Profiles, and Building an AI Brain (MCP)" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you are building for scale, technical debt eventually knocks on the door, this weekend, I answered. Here is what shipped:&lt;/p&gt;

&lt;h2&gt;
  
  
  Sub-Second Search &amp;amp; Pagination Fixes
&lt;/h2&gt;

&lt;p&gt;Thanks to community feedback, I prioritized the "slow search" issue. Digging into the metrics, I found the culprit: classic N+1 query problems that were dragging down the &lt;code&gt;/plugins&lt;/code&gt; list response times.&lt;/p&gt;

&lt;p&gt;I optimized the underlying queries and eager-loaded the necessary relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get my updates in your &lt;strong&gt;inbox&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Register to be the first to receive my new articles on Laravel, DevOps, my projects, and the future of the Laravel package ecosystem.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                &amp;lt;span&amp;gt;Subscribe&amp;lt;/span&amp;gt;
                &amp;lt;span&amp;gt;&amp;lt;svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"&amp;gt;
    &amp;lt;g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round"&amp;gt;
        &amp;lt;g&amp;gt;
            &amp;lt;circle cx="4" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
            &amp;lt;circle cx="12" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
            &amp;lt;circle cx="20" cy="12" r="3"&amp;gt;&amp;lt;/circle&amp;gt;
        &amp;lt;/g&amp;gt;

    &amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;&amp;lt;/span&amp;gt;



            Email sent! Check your inbox to complete your signup.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;No spam. Unsubscribe anytime. Thank you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt; : Search and pagination now load in under 1 second. &lt;a href="https://dev.to/danielpetrica/how-i-built-a-high-performance-directory-with-laravel-octane-and-filament-404p-temp-slug-7692170"&gt;Check here to se how i built the site for that&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lesson&lt;/strong&gt; : Even experienced developers miss N+1 queries sometimes. This reinforces why optimization isn't just "nice to have" it is a core feature that requires constant observability. When you can't measure it, you can't fix it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexghr9xuuwh0rmihgk02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexghr9xuuwh0rmihgk02.png" alt="LaraPlugins Devlog: Sub-Second Search, Vendor Profiles, and Building an AI Brain (MCP)" width="800" height="442"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;LaraPlugins screenshot, what will be your next plugin?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New "Vendor" &amp;amp; "Maintainer" Pages
&lt;/h2&gt;

&lt;p&gt;I have started rolling out dedicated pages for plugin vendors and maintainers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current State&lt;/strong&gt; : Basic listings are live, with clean profiles for major vendors like Spatie, BeyondCode, and others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://laraplugins.io/vendors/spatie?ref=danielpetrica.com" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfsx686af10qsz0nndph.png" alt="LaraPlugins Devlog: Sub-Second Search, Vendor Profiles, and Building an AI Brain (MCP)" width="800" height="481"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Spatie Vendor Page, showcasing all the plugins where they are the vendor.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Goal&lt;/strong&gt; : Turn these into rich profiles with insightful stats like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total downloads across all packages&lt;/li&gt;
&lt;li&gt;Average health score of maintained packages&lt;/li&gt;
&lt;li&gt;Update frequency and responsiveness&lt;/li&gt;
&lt;li&gt;Community trust indicators&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;What specific data points would you want to see on a Maintainer's profile to judge the quality of their work? Let me know in the comments your feedback drives this roadmap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With these new pages my site reached the 10k pages count, are you wondering how I host it to allow for such a size? &lt;a href="https://danielpetrica.com/a-production-ready-laravel-architecture-with-traefik-and-frankenphp/" rel="noopener noreferrer"&gt;Check my stack in this dedicated article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the "Brain" (MCP Server)
&lt;/h2&gt;

&lt;p&gt;This is the most exciting update. I am actively building a &lt;strong&gt;Model Context Protocol (MCP) server&lt;/strong&gt; for LaraPlugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Vision&lt;/strong&gt; : Enable AI agents (like Claude or Cursor) to query my directory directly. Instead of hallucinating packages or using outdated packages due to their cut date, your AI editor could ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"What's the best PDF generation package for Laravel?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Show me the most actively maintained charting libraries"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Find packages that solve authentication with minimal overhead"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI agent would query LaraPlugins in real-time, pulling verified, healthy packages with actual health scores containing "pdf generation" or pdf. No more guessing. No more outdated recommendations. The agent would be then able to choose a plugin based on their health status, php and laravel compatibility, maintenance status and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status&lt;/strong&gt; : The server is live, but search integration is still a Work In Progress. I am currently refining the caching strategy, since Cloudflare cannot easily cache these dynamic API hits, to ensure stability and performance before opening it to a broader audience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why This Matters: Right now, AI models hallucinate package recommendations because they were trained on static data. By providing a real-time MCP interface, I hope to solve a real problem: How do AI agents find the healthiest, most actively maintained Laravel packages?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Quality of Life Updates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Improved version compatibility badges on plugin cards (clearer visual)&lt;/li&gt;
&lt;li&gt;Refined the "Health Score" algorithm (still tuning this, signals are hard!)&lt;/li&gt;
&lt;li&gt;Added breadcrumb navigation to vendors and maintainers for improved discoverability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Tomorrow is for polishing. I will be finalizing the MCP search integration and ensuring the new Vendor index pages are SEO-ready for organic discovery.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For you&lt;/strong&gt; : Give the new search a spin and tell me if it feels snappier. Does the sub-second speed make a difference in how you explore packages? As always, your feedback drives this roadmap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What's coming next&lt;/strong&gt; : In February, I am building curated collections ("Featured: Filament Ecosystem", "Top 10 Database Packages") and opening early access to the MCP server for beta testers. If you want to be notified when the MCP is ready, drop a comment or reach out in any way.&lt;/p&gt;

&lt;p&gt;Until then, happy package hunting.&lt;/p&gt;

</description>
      <category>larapluginsio</category>
      <category>news</category>
      <category>personalprojects</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I built a high-performance directory with Laravel Octane and Filament</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Sun, 11 Jan 2026 12:06:03 +0000</pubDate>
      <link>https://dev.to/danielpetrica/how-i-built-a-high-performance-directory-with-laravel-octane-and-filament-5b4l</link>
      <guid>https://dev.to/danielpetrica/how-i-built-a-high-performance-directory-with-laravel-octane-and-filament-5b4l</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs4g5cpir255wm4dwuom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs4g5cpir255wm4dwuom.png" alt="How I built a high-performance directory with Laravel Octane and Filament" width="799" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back when I launched the survey on the state of the Laravel plugin ecosystem, one statistic stood out immediately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;70%+ of developers spend between 5 and 30 minutes just to determine if a plugin is healthy enough to install&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is " &lt;strong&gt;Dependency Anxiety.&lt;/strong&gt;" It’s the recurring cost we pay every time we search for a package or audit our &lt;code&gt;composer.json&lt;/code&gt;. Is this package abandoned? Does it support PHP 8.5? Are the tests passing? Multiply those 30 minutes by every package in your stack, and the productivity loss is massive.&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;Laraplugins.io&lt;/strong&gt; , an automated intelligence tool designed to do that vetting for you and display it to you so you can get a pretty good idea of the health status in seconds.&lt;/p&gt;

&lt;p&gt;I launched LaraPlugins.io on product hunt today. Please help me get it in front of developers with and upvote and tell me in the comments over there what you would change about my health score algorithm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.producthunt.com/products/laraplugins-io?utm_source=other&amp;amp;utm_medium=social" rel="noopener noreferrer"&gt;Upvote on product hunt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ks2n95ciavx9yaweh69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ks2n95ciavx9yaweh69.png" alt="How I built a high-performance directory with Laravel Octane and Filament" width="799" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Built for Speed and Scale
&lt;/h2&gt;

&lt;p&gt;As a DevOps and developers, I wanted the infrastructure to be as robust as the data it serves. I designed a high-performance architecture capable of handling heavy traffic on a single multi-site VPS running over 30 projects and nearly 100 Docker containers. here more info if curious about my &lt;a href="https://danielpetrica.com/a-production-ready-laravel-architecture-with-traefik-and-frankenphp/" rel="noopener noreferrer"&gt;production-ready FrankenPHP setup.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;The application runs on a modern, containerized stack optimized for concurrency and low latency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; Laravel Octane with FrankenPHP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing:&lt;/strong&gt; Traefik Reverse Proxy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching:&lt;/strong&gt; Redis &amp;amp; Cloudflare CDN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration:&lt;/strong&gt; Docker Compose&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Request Lifecycle
&lt;/h2&gt;

&lt;p&gt;The flow is designed to minimize hit-rate on the application server:&lt;br&gt;&lt;br&gt;
&lt;code&gt;User -&amp;gt; Cloudflare CDN -&amp;gt; Traefik -&amp;gt; FrankenPHP (Octane) -&amp;gt; Redis&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Edge Caching:&lt;/strong&gt; Public pages are cached by Cloudflare for 2 hours. This drastically reduces server load by serving static content directly from the edge.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Routing:&lt;/strong&gt; When traffic reaches my VPS, Traefik handles the routing. It ensures fast, reliable directing of requests across the dozens of micro-services and sites running in parallel.​&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Memory Bootstrapping:&lt;/strong&gt; Finally, Laravel Octane and FrankenPHP take over. Because the application stays booted in memory, we eliminate the traditional PHP-FPM overhead, ensuring responses are nearly instantaneous.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The "Health Score" Algorithm
&lt;/h2&gt;

&lt;p&gt;The core of Laraplugins.io is the Health Score. It’s not a magic number, but an aggregate of about 10 key signals that determine a package's viability.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Update Recency
&lt;/h2&gt;

&lt;p&gt;This is the baseline. Developers need active maintenance. I track the last commit date and release history to ensure you aren't installing abandonware.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Version Compatibility
&lt;/h2&gt;

&lt;p&gt;I rigorously check &lt;code&gt;composer.json&lt;/code&gt; constraints to verify active support for modern Laravel and PHP versions. If a package is stuck on Laravel 9, you need to know.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The "Archived" Penalty
&lt;/h2&gt;

&lt;p&gt;If a project is archived on GitHub, we apply a heavy penalty. This marks a definitive end of life for the package, and it should generally be avoided for new projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Normalized Download Stats
&lt;/h2&gt;

&lt;p&gt;We monitor download counts, but we’ve intentionally reduced their weight in the final score. We want to highlight quality, not just popularity. This ensures new, high-quality plugins aren't unfairly penalized just because they haven't been around for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Best Practices &amp;amp; Quality
&lt;/h2&gt;

&lt;p&gt;Finally, we scan for indicators of a mature engineering culture: presence of CI/CD pipelines, active test suites, and valid licenses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback Welcome
&lt;/h2&gt;

&lt;p&gt;The Health Score is currently in beta. I’m building this for the community, so I want to hear from you. What signals do you look for before composer require-ing a new package?&lt;/p&gt;

&lt;p&gt;Let me know your thoughts, and check out the launch on&lt;a href="https://www.producthunt.com/products/laraplugins-io?utm_source=danielpetrica.com&amp;amp;utm_medium=blog" rel="noopener noreferrer"&gt;product hunt please&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>tool</category>
      <category>traefik</category>
      <category>personalprojects</category>
    </item>
    <item>
      <title>My new project: Coz.jp</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Sun, 09 Nov 2025 06:06:40 +0000</pubDate>
      <link>https://dev.to/danielpetrica/my-new-project-cozjp-45o</link>
      <guid>https://dev.to/danielpetrica/my-new-project-cozjp-45o</guid>
      <description>&lt;p&gt;Hello, Dev Community! I'm excited to finally pull back the curtain on my latest side project: Coz.jp, a new link shortening service.&lt;/p&gt;

&lt;p&gt;While "yet another link shortener" might sound simple, the goal here was to build something fast, reliable, and deeply integrated, all while using a modern and scalable tech stack. I want to share a little bit about what it does and the architecture I chose.&lt;/p&gt;

&lt;p&gt;Why I Built It&lt;/p&gt;

&lt;p&gt;As a developer, I've always needed a link shortener that gives me full control after the link is created. That's what Coz.jp is all about: fast creation, but also the ability to go back and fix errors by editing the target URL, or deleting the link entirely. It focuses on:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fast and Reliable URL Shortening: Speed is non-negotiable for link redirects.

Complete Link Management: Easily fix mistakes or update destinations after publishing.

Privacy-Friendly Analytics: Gain insights without compromising user security.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Technical Core: My Stack&lt;/p&gt;

&lt;p&gt;For a project like this, I needed a setup that was robust, easy to deploy, and scalable. I went with a modern, containerized architecture that uses some of my favorite tools:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Framework: Laravel The back-end is powered by Laravel. Its elegant syntax and robust features made developing the API endpoints and the link management dashboard incredibly efficient. Laravel's Eloquent ORM provides a secure and structured way to handle the thousands of daily redirects.

Containerization: Docker Compose Everything runs inside Docker Compose. This ensures that the local development environment is an exact mirror of the production environment, eliminating the dreaded "it works on my machine" problem and making deployment rock-solid.

Database: MySQL The reliable backbone of the application is MySQL. It handles the quick lookups required for redirects and securely stores all the user and link data.

Reverse Proxy: Traefik I used Traefik as the edge router. Traefik handles all the incoming traffic, manages HTTPS certificates automatically, and routes traffic efficiently to the correct Docker containers. This setup gives me excellent performance and a truly scalable, production-ready infrastructure.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ready for Your Workflow&lt;/p&gt;

&lt;p&gt;One of the key features I focused on was developer and business integration. Coz.jp offers:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Full API Integration: Ready for you to integrate into scripts or custom applications.

N8N Node: For those using low-code automation, I've built an n8n node to allow seamless integration into your automated workflows.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can manage your links with a simple email verification process, ensuring only you control your shortened URLs.&lt;/p&gt;

&lt;p&gt;Try It Out!&lt;/p&gt;

&lt;p&gt;I'd love for you to check it out. &lt;a&gt;You can login and Start your free trial today.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A Production-Ready Laravel Architecture with Traefik and FrankenPHP</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Sun, 09 Nov 2025 05:49:04 +0000</pubDate>
      <link>https://dev.to/danielpetrica/a-production-ready-laravel-architecture-with-traefik-and-frankenphp-1led</link>
      <guid>https://dev.to/danielpetrica/a-production-ready-laravel-architecture-with-traefik-and-frankenphp-1led</guid>
      <description>&lt;p&gt;Laravel is a very performant framework, but a standard architecture has one big flaw derived from how PHP works: It has to rebuild the entire framework on every single request.&lt;/p&gt;

&lt;p&gt;Even with optimizations, this process still takes 40 to 60 ms on my machine with PHP 8.4. Luckily, for years, the PHP and Laravel worlds have had a solution that dramatically reduces this load time: Laravel Octane and FrankenPHP. The booting time for the Laravel framework can drop to just 4 to 6 ms per request. Incredible, isn't it?&lt;/p&gt;

&lt;p&gt;Now, if you're new to Laravel Octane or FrankenPHP, you may wonder: How is this possible?&lt;/p&gt;

&lt;p&gt;The simple answer is that the framework is kept in memory. After FrankenPHP starts, Laravel is always ready to serve requests without a full reboot. The real explanation is more complex and out of scope for this article. If you're curious, you can read the official Laravel Octane and FrankenPHP docs for a deeper dive.&lt;/p&gt;

&lt;p&gt;read the full article at: &lt;a href="https://coz.jp/IWz4E3" rel="noopener noreferrer"&gt;DanielPetrica.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>webdev</category>
      <category>programming</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Laravel and Traefik: Dynamic Configuration for Effortless Multi-Domain Management</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Sat, 13 Sep 2025 16:34:05 +0000</pubDate>
      <link>https://dev.to/danielpetrica/laravel-and-traefik-dynamic-configuration-for-effortless-multi-domain-management-4kni</link>
      <guid>https://dev.to/danielpetrica/laravel-and-traefik-dynamic-configuration-for-effortless-multi-domain-management-4kni</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xpmk0a1j9qa9nvo0l6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xpmk0a1j9qa9nvo0l6d.png" alt="Laravel and Traefik: Dynamic Configuration for Effortless Multi-Domain Management" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After finishing my full-time employment, I suddenly found myself with a lot of free time. To make use of it, I decided to stay up to date by working on some new side projects and experiments.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge: Dynamic Multi-Domain Support in Laravel with Traefik
&lt;/h3&gt;

&lt;p&gt;One of these experiments was related to supporting multiple domains in Laravel in a dynamic way. I initially found this relatively easy, but in a hypothetical production environment, handling everything automatically would not be quite as seamless. For example, Traefik usually relies on label-based or file-based configurations, which means the &lt;em&gt;dynamic&lt;/em&gt; part disappears entirely, leaving you with one big configuration file to manage manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovering Traefik’s HTTP Configuration Provider
&lt;/h3&gt;

&lt;p&gt;Just when I was about to abandon this project, I remembered the existence of the HTTP configuration provider in the Traefik documentation. According to the docs, it allows you to configure Traefik by pointing it to a specific URL that returns either a JSON or YAML configuration.&lt;/p&gt;

&lt;p&gt;That’s when inspiration struck: I could configure Laravel to expose a JSON configuration representing the router setup. This configuration would then point to a single service defined by Docker service labels.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the Docker Service Labels
&lt;/h3&gt;

&lt;p&gt;With some blind trust, I jumped into implementing the idea. Creating the service labels was fairly easy. I also included a default domain host to ensure I always had an accessible admin interface. Here’s the snippet from my &lt;code&gt;compose.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;     &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.enable=true&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.example-name-https.rule=Host(`example-name`)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.example-name-https.tls=true&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.services.example-name-service.loadbalancer.server.port=80&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.example-name-https.tls.certresolver=cloudflare&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.example-name-https.entrypoints=websecure&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.example-name-https.middlewares=forward-auth_blog-network"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.forward-auth_example-name.headers.customrequestheaders.X-Forwarded-Proto=https"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.forward-auth_example-name.headers.customrequestheaders.X-Forwarded-Host=example-name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generating the Dynamic Configuration in Laravel
&lt;/h2&gt;

&lt;p&gt;On the Laravel side, I was able to expose a JSON structure fairly easily. Since I had a collection of domains to generate, I prepared a multidimensional array like the one in the snippet below:&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;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'http'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'routers'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$domainName&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'-https'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'rule'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Host(`'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="mf"&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;'tls'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'service'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$serviceName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'middlewares'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$middlewareName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'http'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'middlewares'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$middlewareName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'headers'&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;'customrequestheaders'&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;'X-Forwarded-Proto'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'https'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'X-Forwarded-Host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$host&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;If you’re already familiar with YAML or Traefik configuration, you can see this structure is very similar to the earlier Docker labels example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Traefik to Consume Laravel’s Endpoint
&lt;/h3&gt;

&lt;p&gt;At this point, all I had to do was tell Traefik to call this new Laravel-powered endpoint. In &lt;code&gt;traefik.yml&lt;/code&gt;, I went to the &lt;code&gt;providers&lt;/code&gt; section and added the following HTTP provider configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://admin.example.com/internal/traefik_config"&lt;/span&gt;
  &lt;span class="na"&gt;pollInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15s"&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;customRequestHeaders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;X-Traefik-Passphrase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yoursecrettoken"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup is straightforward. You specify the endpoint (reachable from the Traefik container), configure the polling interval to tell Traefik how often it should check for updates, and optionally add headers to secure requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Setback: JSON Format Issues
&lt;/h3&gt;

&lt;p&gt;At this point I tested everything, hopeful that it would just work. Obviously, that wasn’t the case. After trying different versions and settings — and reading through forum discussions — I realized nobody had been able to get the JSON HTTP provider working properly. It made me suspect the documentation was outdated, so I started looking for alternatives.&lt;/p&gt;

&lt;p&gt;The breakthrough came when I decided to serve the configuration in YAML instead of JSON. I chose the &lt;code&gt;symfony/yaml&lt;/code&gt; library to convert my Laravel arrays into YAML format easily. The updated Laravel response looked like this:&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;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Yaml&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$config&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="n"&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="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/yaml'&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;
  
  
  Final Results and Practical Benefits
&lt;/h3&gt;

&lt;p&gt;After configuring Cloudflare to point to the correct server and managing domains through Laravel, I achieved the dynamic multi-domain setup I aimed for. Traefik not only serves the domains correctly but also respects domain removals—after the polling interval, removed domains cease to be reachable.&lt;/p&gt;

&lt;p&gt;This seamless integration between Laravel and Traefik solves a complex problem and will form the foundation for a new tool I’m developing to improve Laravel developers’ workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to Stay Updated?
&lt;/h2&gt;

&lt;p&gt;If you’re interested in early updates, pre-launch offers, or beta invitations for this tool, please subscribe to my website. Your support means a lot on this journey!&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;This project is a proof of concept showcasing how dynamic domain routing can be effectively managed by Laravel in conjunction with Traefik, combining automation and flexibility for scalable deployments.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>guides</category>
      <category>personalprojects</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Self-Hosting n8n with Docker Compose and Traefik for AI-Powered Workflow Automation</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Mon, 07 Jul 2025 22:27:10 +0000</pubDate>
      <link>https://dev.to/danielpetrica/self-hosting-n8n-with-docker-compose-and-traefik-for-ai-powered-workflow-automation-2efc</link>
      <guid>https://dev.to/danielpetrica/self-hosting-n8n-with-docker-compose-and-traefik-for-ai-powered-workflow-automation-2efc</guid>
      <description>&lt;h3&gt;
  
  
  &lt;strong&gt;Tired of juggling apps and manual tasks?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1677442135703-1787eea5ce01%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDE4fHxhaSUyMGFnZW50fGVufDB8fHx8MTc1MTg4MzUzNnww%26ixlib%3Drb-4.1.0%26q%3D80%26w%3D2000" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1677442135703-1787eea5ce01%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDE4fHxhaSUyMGFnZW50fGVufDB8fHx8MTc1MTg4MzUzNnww%26ixlib%3Drb-4.1.0%26q%3D80%26w%3D2000" alt="Self-Hosting n8n with Docker Compose and Traefik for AI-Powered Workflow Automation" width="2000" height="1125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Self-hosting n8n could be a game-changer. It’s a powerful, open-source automation tool, and setting it up yourself using Docker Compose and Traefik makes it both flexible and super accessible. I'll walk you through the setup and show off the cool AI features that really make n8n shine.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;So, what exactly is n8n?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Think of n8n as your digital duct tape. It's a free, open-source tool that lets you visually connect different apps and services to automate just about any workflow you can imagine. Drag and drop nodes to build sequences that handle all those boring, repetitive tasks for you. It saves a ton of time and gives you way more control over your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want my expertize applied to you company? Contact me
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;n8n's Secret Sauce: AI Power&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;n8n isn't just about simple "if this, then that" rules anymore. It's evolved into a legit platform for building &lt;em&gt;smart&lt;/em&gt;, AI-driven applications. How? By using something called "agentic AI." This means your workflows can actually make decisions, understand context, and interact with your data intelligently, not just follow a rigid script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Here’s what makes the AI side so powerful:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The AI Agent Node:&lt;/strong&gt; This is the brains of the operation. It processes info, makes choices, and uses built-in "tools" to talk to other apps or data sources. It connects to big AI models like OpenAI's GPT, Google's Gemini, or Anthropic's Claude to understand what you need and give smart replies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LangChain Built Right In:&lt;/strong&gt; n8n plays nicely with LangChain, a popular framework for building complex AI logic. This means you can create chains of AI actions or even agents that decide &lt;em&gt;which&lt;/em&gt; actions to take next, all within your workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Talk to Your Data (Vector DBs):&lt;/strong&gt; Connect n8n to vector databases like Pinecone or Supabase, or use its simple built-in memory store. This is key for building RAG systems – where the AI can pull answers &lt;em&gt;from your own private docs or data&lt;/em&gt;, making it super relevant and useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endless Possibilities:&lt;/strong&gt; With these tools, you can build things like: smart chatbots for customers, AI assistants that summarize your emails and book meetings, or systems that dig insights out of messy, unstructured data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What You'll Need Before We Start&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Don't sweat it, it's mostly standard gear for self-hosters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker &amp;amp; Docker Compose:&lt;/strong&gt; Make sure they're installed and running. (Docker Compose usually comes bundled with Docker these days).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traefik:&lt;/strong&gt; You'll need this running for your reverse proxy. &lt;em&gt;(If you're not set up yet, check out this guide on configuring Traefik).&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Domain Name:&lt;/strong&gt; Grab a domain or subdomain to point to your n8n instance (places like Namecheap often have domains starting around $2).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Docker Compose Setup&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We'll use a &lt;code&gt;compose.yaml&lt;/code&gt; file to spin up both n8n and its database (we're using PostgreSQL here). Traefik integration is handled smoothly with labels right in the file – it takes care of routing traffic and HTTPS automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the &lt;code&gt;compose.yaml&lt;/code&gt; file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;n8n&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8nio/n8n&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:5678:5678"&lt;/span&gt; &lt;span class="c1"&gt;# Only exposed locally, Traefik handles external access&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_TYPE=postgresdb&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_HOST=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_PORT=5432&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_DATABASE=${POSTGRES_DB}&lt;/span&gt; &lt;span class="c1"&gt;# Comes from .env file&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_USER=${POSTGRES_USER}&lt;/span&gt; &lt;span class="c1"&gt;# Comes from .env file&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}&lt;/span&gt; &lt;span class="c1"&gt;# Comes from .env file&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_FUNCTION_ALLOW_EXTERNAL=langchain&lt;/span&gt; &lt;span class="c1"&gt;# Important for LangChain nodes!&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;n8n_data:/home/node/.n8n&lt;/span&gt; &lt;span class="c1"&gt;# Persist your workflows and settings&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Traefik magic happens here&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)"&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;&amp;lt;&amp;lt; REPLACE WITH YOUR DOMAIN!&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.n8n.entrypoints=websecure"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.n8n.tls.certresolver=myresolver"&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;&amp;lt;&amp;lt; REPLACE 'myresolver' with your Traefik resolver name!&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.services.n8n.loadbalancer.server.port=5678"&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n_postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=${POSTGRES_DB}&lt;/span&gt; &lt;span class="c1"&gt;# From .env&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=${POSTGRES_USER}&lt;/span&gt; &lt;span class="c1"&gt;# From .env&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=${POSTGRES_PASSWORD}&lt;/span&gt; &lt;span class="c1"&gt;# From .env&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt; &lt;span class="c1"&gt;# Persist your database&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;n8n_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;n8n&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Assumes your Traefik network already exists&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don't forget the .env file!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the same folder as your &lt;code&gt;compose.yaml&lt;/code&gt; to keep your database secrets safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;n8n&lt;/span&gt;
&lt;span class="py"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;n8nuser&lt;/span&gt;
&lt;span class="py"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;a_very_strong_and_secret_password # Make it a good one!&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Let's Get n8n Running!&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Make a Home:&lt;/strong&gt; Create a directory for your n8n setup and save both the &lt;code&gt;compose.yaml&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt; files inside it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traefik Takes Over:&lt;/strong&gt; Traefik should automatically spot your new service, grab an SSL certificate, and start routing traffic to &lt;code&gt;https://n8n.yourdomain.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Almost There!&lt;/strong&gt; Once the containers are running (give it a minute after the command finishes), open your domain in a web browser. The n8n web interface will greet you and guide you through the initial user setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breathe Easy:&lt;/strong&gt; Notice the n8n port (&lt;code&gt;5678&lt;/code&gt;) is only exposed &lt;em&gt;locally&lt;/em&gt; on your server. Traefik is the secure gatekeeper handling all outside traffic. we can go even further, on my server I don't even expose that port internally as it's not needed and better not expose things we don't need. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Fire it Up:&lt;/strong&gt; Open a terminal, navigate to that directory, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Your self-hosted n8n automation playground is ready. Go build something awesome!&lt;/p&gt;

&lt;p&gt;Want to learn how to secure your database? I have an article about that&lt;/p&gt;

&lt;p&gt;[&lt;/p&gt;

&lt;p&gt;Easy database backups with docker-compose&lt;/p&gt;

&lt;p&gt;I implemented a backup tool for my new docker compose and traefik stack to help save and restore my databases and avoid loss of data&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnm0rqub1ucpzzz5whyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnm0rqub1ucpzzz5whyu.png" alt="Self-Hosting n8n with Docker Compose and Traefik for AI-Powered Workflow Automation" width="256" height="256"&gt;&lt;/a&gt;Daniel PetricaDaniel, Andrei-Daniel Petrica&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdanielpetrica.com%2Fcontent%2Fimages%2Fthumbnail%2Fphoto-1575729853562-e3ad7084c28d" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdanielpetrica.com%2Fcontent%2Fimages%2Fthumbnail%2Fphoto-1575729853562-e3ad7084c28d" alt="Self-Hosting n8n with Docker Compose and Traefik for AI-Powered Workflow Automation" width="2000" height="1333"&gt;&lt;/a&gt;&lt;br&gt;
](&lt;a href="https://danielpetrica.com/easy-database-backups-with-docker-compose/" rel="noopener noreferrer"&gt;https://danielpetrica.com/easy-database-backups-with-docker-compose/&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If N8N is not your sauce active pieces is actually a great compelling alternative too&lt;/p&gt;

&lt;p&gt;[&lt;/p&gt;

&lt;p&gt;Install Activepieces with Docker &amp;amp; Traefik for self-hosted automation&lt;/p&gt;

&lt;p&gt;Activepieces is an open-source automation tool enabling users to create custom workflows across many apps. We’ll install it on docker and traefik&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdy1lceo27d0zozzki0mr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdy1lceo27d0zozzki0mr.png" alt="Self-Hosting n8n with Docker Compose and Traefik for AI-Powered Workflow Automation" width="256" height="256"&gt;&lt;/a&gt;Daniel PetricaDaniel, Andrei-Daniel Petrica&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41qqvjfbnsee03485a5q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41qqvjfbnsee03485a5q.webp" alt="Self-Hosting n8n with Docker Compose and Traefik for AI-Powered Workflow Automation" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
](&lt;a href="https://danielpetrica.com/installing-activepieces-with-docker-compose-and-traefik/" rel="noopener noreferrer"&gt;https://danielpetrica.com/installing-activepieces-with-docker-compose-and-traefik/&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Want my expertize applied to you company? Contact me
&lt;/h2&gt;

</description>
      <category>ai</category>
      <category>guides</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Tired of Manual Calendar Management? I Was Too. So I Built This Tool</title>
      <dc:creator>Daniel, Petrica Andrei-Daniel</dc:creator>
      <pubDate>Mon, 07 Jul 2025 22:21:06 +0000</pubDate>
      <link>https://dev.to/danielpetrica/tired-of-manual-calendar-management-i-was-too-so-i-built-this-tool-4p36</link>
      <guid>https://dev.to/danielpetrica/tired-of-manual-calendar-management-i-was-too-so-i-built-this-tool-4p36</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8d45pw3d9a0lsi0ny751.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8d45pw3d9a0lsi0ny751.png" alt="Tired of Manual Calendar Management? I Was Too. So I Built This Tool" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lately, I’ve been trying to organize my life better. Like many people, I kept hearing about &lt;strong&gt;calendar blocking&lt;/strong&gt; and other productivity techniques.&lt;/p&gt;

&lt;p&gt;But let’s be honest — am I really expected to manually create countless little event blocks, keep track of holidays, and constantly update my calendar?&lt;/p&gt;

&lt;p&gt;No thanks. Instead, &lt;strong&gt;I built a tool that does it all for me&lt;/strong&gt; — fast, efficiently, and with no friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Tool Does
&lt;/h2&gt;

&lt;p&gt;This streamlined web app lets you build complex, recurring schedules in just a few clicks. Define your time blocks once, customize your settings, and instantly generate a complete calendar file ready to import into any major calendar application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want my expertize applied to you company? Contact me
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Multiple Time Slots&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Create recurring events with custom titles, days, and times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexible Scheduling&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Set schedules on any combination of days — weekdays, weekends, or fully custom patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart Off-Days&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Exclude specific dates, such as holidays or vacations, where your recurring events shouldn’t apply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time Tracking&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Get an overview of how many events will be created and the total time commitment involved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dark Mode Support&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Includes automatic dark mode detection for a more comfortable viewing experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsive Design&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Works seamlessly on both desktop and mobile devices.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ideal Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Work Schedules&lt;/strong&gt; — Block out time for focus sessions, meetings, or project work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fitness Routines&lt;/strong&gt; — Plan gym sessions, yoga classes, or training blocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Study Plans&lt;/strong&gt; — Organize consistent learning sessions across subjects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal Projects&lt;/strong&gt; — Allocate time for hobbies, side gigs, or creative work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Coordination&lt;/strong&gt; — Create shared, recurring schedules for collaborative tasks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set Your Date Range&lt;/strong&gt;
Define the start and end dates for your schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure Time Slots&lt;/strong&gt;
Add as many recurring time blocks as needed, each with its own settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclude Off-Days&lt;/strong&gt;
Remove holidays, breaks, or other non-working days from your schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate and Download&lt;/strong&gt;
Export your setup as a standard &lt;code&gt;.ics&lt;/code&gt; calendar file, compatible with Google Calendar, Outlook, Apple Calendar, and others.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;If you're tired of repeating the same scheduling steps week after week, this tool will change how you manage your time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://danielpetrica.com/periodic-schedule-calendar-generator/?adaoidad=adadaq" rel="noopener noreferrer"&gt;&lt;strong&gt;Visit the Periodic Schedule Calendar Generator&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Manual scheduling is tedious and error-prone. This tool eliminates that hassle. With just a few clicks, it generates hundreds of recurring events, accurately accounts for off-days, and gives you insight into your total time allocation before you even start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Built using &lt;strong&gt;Vue 3&lt;/strong&gt; and &lt;strong&gt;Tailwind CSS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Pure &lt;strong&gt;JavaScript&lt;/strong&gt; generation of &lt;code&gt;.ics&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;No external dependencies beyond CDN libraries&lt;/li&gt;
&lt;li&gt;Fully &lt;strong&gt;responsive design&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Compatible with all major calendar platforms&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Built with Vue.js. Designed for clarity. Powered by smart scheduling logic.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Say goodbye to manual calendar entry — and take control of your time.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>tool</category>
      <category>productivity</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
