<?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: Antonios Thanasis</title>
    <description>The latest articles on DEV Community by Antonios Thanasis (@antoniosthanasisgit).</description>
    <link>https://dev.to/antoniosthanasisgit</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1001182%2F6e93ea0d-d759-4038-954e-46a1fcd85503.jpg</url>
      <title>DEV Community: Antonios Thanasis</title>
      <link>https://dev.to/antoniosthanasisgit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/antoniosthanasisgit"/>
    <language>en</language>
    <item>
      <title>How to Save Money Using Free AI Tools (No Paid Subscription Needed)</title>
      <dc:creator>Antonios Thanasis</dc:creator>
      <pubDate>Tue, 05 May 2026 10:10:32 +0000</pubDate>
      <link>https://dev.to/antoniosthanasisgit/how-to-save-money-using-free-ai-tools-without-paying-for-multiple-subscriptions-3dfj</link>
      <guid>https://dev.to/antoniosthanasisgit/how-to-save-money-using-free-ai-tools-without-paying-for-multiple-subscriptions-3dfj</guid>
      <description>&lt;p&gt;I'm not claiming this is the best approach. This is simply what I do, and it genuinely helps me. If it helps you too, great.*&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Most developers are overpaying for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Pro&lt;/li&gt;
&lt;li&gt;ChatGPT Plus
&lt;/li&gt;
&lt;li&gt;Cursor Pro
&lt;/li&gt;
&lt;li&gt;GitHub Copilot (paid plan)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The truth? &lt;strong&gt;Free tiers are powerful enough&lt;/strong&gt; if you combine them, switch when limits hit, and avoid wasting tokens on tasks you can do yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Strategy: Rotation Over Subscription
&lt;/h2&gt;

&lt;p&gt;Instead of committing to one paid tool, I keep a &lt;strong&gt;roster of free AI assistants&lt;/strong&gt; and cycle through them based on task complexity and remaining quota.&lt;/p&gt;

&lt;p&gt;My always-available lineup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt; (free tier) – agentic coding with project context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot&lt;/strong&gt; (free tier / limited access) – inline autocomplete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windsurf&lt;/strong&gt; (free tier) – multi-file edits through natural language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; (browser, free) – complex reasoning &amp;amp; architecture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT&lt;/strong&gt; (free tier) – general fallback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; (browser, free) – another fallback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek&lt;/strong&gt; (browser) – overflow capacity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single tool is my “main”. They all are.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Use Each Free Tier Efficiently
&lt;/h2&gt;

&lt;p&gt;I think of tasks in three difficulty levels. Matching the tool to the task preserves high-value quotas.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟢 Simple Coding Tasks
&lt;/h3&gt;

&lt;p&gt;These need speed, not deep thinking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast autocomplete → &lt;strong&gt;GitHub Copilot&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Basic refactors, single-file edits → &lt;strong&gt;Cursor&lt;/strong&gt; basic agent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🟡 Medium Complexity
&lt;/h3&gt;

&lt;p&gt;Multi-file changes, understanding project-wide impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-file edits with natural language → &lt;strong&gt;Windsurf&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Project-level changes, agentic refactors → &lt;strong&gt;Cursor&lt;/strong&gt; agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔴 Complex Architecture &amp;amp; Reasoning
&lt;/h3&gt;

&lt;p&gt;When I need to think through hard problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System design discussions&lt;/li&gt;
&lt;li&gt;Debugging complex logic&lt;/li&gt;
&lt;li&gt;Refactoring strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;→ Use &lt;strong&gt;browser-based Claude&lt;/strong&gt; (no code execution needed, just reasoning)&lt;/p&gt;




&lt;h2&gt;
  
  
  Make Every Token Count: Use AI Selectively, Not Blindly
&lt;/h2&gt;

&lt;p&gt;Free tiers have limits. The best way to stretch them is to &lt;strong&gt;only involve AI where it adds real value&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Use AI for complexity, not routine
&lt;/h3&gt;

&lt;p&gt;I don’t copy-paste simple Trello/Linear tickets into a chat window. If the task is straightforward (rename a variable, add a basic endpoint), I just code it. I reserve AI for the hard parts: designing architecture, solving tricky bugs, or optimizing logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Form my own idea first
&lt;/h3&gt;

&lt;p&gt;Before asking AI to “build feature X,” I think through a rough approach. I write a quick outline or a dirty first draft — then I ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s my plan for the auth flow [describe it]. Is this a good approach, and how should I refactor it?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now I’m using AI as a reviewer and a refactoring partner, not as a code generator from scratch. This saves tokens and produces better output because I provide context.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Ask for opinions, not just code
&lt;/h3&gt;

&lt;p&gt;Instead of “write a caching layer,” I try:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I’m thinking of using Redis for session caching. Does that make sense for my use case, or is there a simpler pattern?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I get high-value reasoning without burning quota on boilerplate the AI would have generated anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Write quick-and-dirty, then request a refactor
&lt;/h3&gt;

&lt;p&gt;I stub out the core logic myself — even if it’s messy. Then I feed it to the AI with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s a rough implementation. How can I make this cleaner or more idiomatic?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I learn more, keep control of the design, and the AI focuses on exactly what I need: improving existing code, not guessing requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; I stay in the driver’s seat. The AI augments my thinking, not replaces it — and my free quotas last far longer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Do When I Hit a Limit
&lt;/h2&gt;

&lt;p&gt;Limits will happen. &lt;strong&gt;I don’t stop working — I switch.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If this limit is hit&lt;/th&gt;
&lt;th&gt;I switch to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cursor free quota&lt;/td&gt;
&lt;td&gt;Copilot or Windsurf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot free quota&lt;/td&gt;
&lt;td&gt;Cursor or browser tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windsurf free quota&lt;/td&gt;
&lt;td&gt;Cursor agents or Claude browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All desktop app limits&lt;/td&gt;
&lt;td&gt;Claude / ChatGPT / Gemini / DeepSeek (browser)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I keep rotating. I treat each tool like a resource bar — when one is empty, the next is full.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Works (for me)
&lt;/h2&gt;

&lt;p&gt;Most people overpay because they treat one tool as their only source of AI assistance. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free tiers have different limits.&lt;/strong&gt; Cursor gives fast requests, Copilot offers completions, Claude browser offers high reasoning depth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser-based AIs are unlimited in sessions&lt;/strong&gt; (you just wait or switch accounts). They're perfect for complex thinking I'd otherwise do inside Cursor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I rarely need all modalities at once.&lt;/strong&gt; When I'm doing heavy reasoning in Claude, I’m not burning Cursor quota.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Plus:&lt;/strong&gt; By using AI only for the genuinely hard parts and feeding it my own ideas, I drastically cut unnecessary token consumption. The sum of free tiers + smart usage &amp;gt; any single paid plan.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mindset Shift That Helps Me
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't rely on one subscription. Tools are interchangeable. Limits are temporary. Switching is part of the workflow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't outsource your thinking — use AI to sharpen it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I stopped treating AI like a paid service I need to subscribe to. I think of myself as a systems operator managing multiple resources, and a craftsman who knows when to reach for a power tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Final Free Stack
&lt;/h2&gt;

&lt;p&gt;These are all set up, no credit card required (or included in free plans I already had):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Cursor (free)&lt;/li&gt;
&lt;li&gt;✅ GitHub Copilot (free tier)&lt;/li&gt;
&lt;li&gt;✅ Windsurf (free)&lt;/li&gt;
&lt;li&gt;✅ Claude (browser, free tier)&lt;/li&gt;
&lt;li&gt;✅ ChatGPT (free)&lt;/li&gt;
&lt;li&gt;✅ Gemini (browser, free)&lt;/li&gt;
&lt;li&gt;✅ DeepSeek (browser, overflow)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total cost: $0/month.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Coverage: full-stack coding, architecture, debugging, and learning.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result (what I get out of it)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full coding coverage&lt;/strong&gt; — from autocomplete to system design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No subscription cost&lt;/strong&gt; — I keep my money for compute, domains, or coffee&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always-on backup&lt;/strong&gt; — no single point of failure, no “service down” panic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible workflow&lt;/strong&gt; — adapts to any task, any language, any time of day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better code&lt;/strong&gt; — because I'm thinking first and using AI to refine, not to guess&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again: this isn't some universal "best way." It's just what I do, and it honestly helps me. Maybe it can help someone else too.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>🐱 Stop Using `env()` in Runtime Code</title>
      <dc:creator>Antonios Thanasis</dc:creator>
      <pubDate>Mon, 27 Apr 2026 08:36:36 +0000</pubDate>
      <link>https://dev.to/antoniosthanasisgit/stop-using-env-in-runtime-code-58hd</link>
      <guid>https://dev.to/antoniosthanasisgit/stop-using-env-in-runtime-code-58hd</guid>
      <description>&lt;h2&gt;
  
  
  The Symptoms 😿
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Jobs fail randomly&lt;/li&gt;
&lt;li&gt;API calls return &lt;code&gt;401 Unauthorized&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The "Solution" 🔄
&lt;/h2&gt;

&lt;p&gt;php artisan config:clear&lt;/p&gt;

&lt;p&gt;Then restart workers to pick up fresh environment values.&lt;/p&gt;

&lt;p&gt;It works temporarily. Then fails again.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem 🐛
&lt;/h2&gt;

&lt;p&gt;The app was configured correctly. All env() calls lived inside config files. Config caching worked perfectly.&lt;/p&gt;

&lt;p&gt;Then a developer added this directly in runtime code:&lt;/p&gt;

&lt;p&gt;$newApiKey = env('NEW_API_KEY');&lt;/p&gt;

&lt;p&gt;No config file entry. No config('services.new_api_key'). Just env() in the middle of a controller.&lt;/p&gt;

&lt;p&gt;It worked fine in development because .env was being loaded on every request.&lt;/p&gt;

&lt;p&gt;But in production, config:cache was running on every container restart. Laravel stops loading .env files when config is cached.&lt;/p&gt;

&lt;p&gt;The config cache didn't have NEW_API_KEY because there was no config file for it. So env('NEW_API_KEY') returned null.&lt;/p&gt;

&lt;p&gt;Boom. null → Unauthorized → crash.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why config:clear "Fixes" It 🔁
&lt;/h2&gt;

&lt;p&gt;config:clear removes the cached config file. Laravel starts loading .env fresh again.&lt;/p&gt;

&lt;p&gt;env('NEW_API_KEY') works again → everything runs fine.&lt;/p&gt;

&lt;p&gt;Until the next container restart runs config:cache and breaks it again.&lt;/p&gt;

&lt;p&gt;It's a temporary bandage. Not a fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Actual Fix ✅
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add the new env variable to the proper config file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// config/services.php&lt;br&gt;
return [&lt;br&gt;
    'api' =&amp;gt; [&lt;br&gt;
        'key' =&amp;gt; env('API_KEY', ''),&lt;br&gt;
        'new_key' =&amp;gt; env('NEW_API_KEY', ''),  // Add this line&lt;br&gt;
    ],&lt;br&gt;
];&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use config() in runtime code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// ❌ Don't do this&lt;br&gt;
$newApiKey = env('NEW_API_KEY');&lt;/p&gt;

&lt;p&gt;// ✅ Do this instead&lt;br&gt;
$newApiKey = config('services.api.new_key');&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redeploy and cache&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;php artisan config:cache&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;✅ No more random Unauthorized errors&lt;/li&gt;
&lt;li&gt;✅ No more config:clear bandages&lt;/li&gt;
&lt;li&gt;✅ New env variables work with config cache&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Lesson 🐾
&lt;/h2&gt;

&lt;p&gt;If your fix is config:clear + restart, that's not a fix — it's a symptom.&lt;/p&gt;

&lt;p&gt;One hard rule:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every new env() call must first go through a config file.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;config file ← env()&lt;/li&gt;
&lt;li&gt;runtime code ← config()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No exceptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐱 Remember
&lt;/h2&gt;

&lt;p&gt;Config files are the bridge between .env and your code.&lt;/p&gt;

&lt;p&gt;Break the bridge, break the app.&lt;/p&gt;

&lt;p&gt;Now go fix your config.&lt;/p&gt;

&lt;p&gt;Stop clearing. Start fixing.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>codequality</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>How We Fixed a Puppeteer Memory Leak in a Laravel IoT App</title>
      <dc:creator>Antonios Thanasis</dc:creator>
      <pubDate>Tue, 03 Mar 2026 10:02:25 +0000</pubDate>
      <link>https://dev.to/antoniosthanasisgit/-how-a-missing-finally-block-was-eating-300mb-of-ram-per-failed-scrape-5179</link>
      <guid>https://dev.to/antoniosthanasisgit/-how-a-missing-finally-block-was-eating-300mb-of-ram-per-failed-scrape-5179</guid>
      <description>&lt;p&gt;We were running an IoT application on Azure and kept hitting a frustrating problem — randomly, Docker commands would hang or fail entirely. No obvious reason. Just… dead.&lt;/p&gt;

&lt;p&gt;After digging, we found the culprit: a &lt;strong&gt;Puppeteer scraper silently leaking Chromium processes&lt;/strong&gt; every time it ran multiple scrapes.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Investigation
&lt;/h2&gt;

&lt;p&gt;First thing we checked was memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps aux &lt;span class="nt"&gt;--sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-%mem | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We spotted multiple orphaned Chromium processes, each eating ~90MB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;green  10374  0.1  1.1  /usr/lib/chromium/chrome --type=renderer --headless ...
green  10367  0.1  1.1  /usr/lib/chromium/chrome --type=renderer --headless ...
green  10347  0.9  1.0  /usr/lib/chromium/chrome --headless --enable-automation ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker stats confirmed the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.Name}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.MemUsage}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scraper_1       320.5MiB / 7.772GiB  ← 🚨
app_1           143.8MiB / 7.772GiB
queue_worker_1  120.5MiB / 7.772GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scraper container was using much more memory than expected, and it kept growing over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  🕵️ The Root Cause
&lt;/h2&gt;

&lt;p&gt;The scraper visits router admin pages using Puppeteer to collect bandwidth data. Originally, the code launched one browser per device:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sequential failures left Chromium processes orphaned.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memory usage grew with each failed scrape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker eventually ran out of memory or hung.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Original Problematic Pattern&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Launches a browser per URL&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// scrape logic&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// only runs on success&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// may never reach browser.close()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Each failure left a Chromium instance alive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With 15+ routers, these quickly stacked up.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✅ The Fix: Single Browser, Multiple Pages
&lt;/h2&gt;

&lt;p&gt;Instead of launching a browser per device, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Launch one shared browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open/close a page per URL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always close the browser in a finally block.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Runner (scraper logic)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Go to the router URL&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Fill in login form&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="username"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;teltonika_username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[name="password"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;teltonika_password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// Wait for page to load after login&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Scrape the values&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#lb_tx_sum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;received&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#lb_rx_sum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total_today_usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#lb_all_sum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total_today_usage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// let the caller handle errors&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Always close the page to prevent leaks&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Main Scraper Loop&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUPPETEER_EXECUTABLE_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--ignore-certificate-errors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-sandbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disable-dev-shm-usage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;teltonika&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="c1"&gt;// send results to API&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ✅ guaranteed cleanup&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📊 Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scraper container memory&lt;/td&gt;
&lt;td&gt;320.5 MiB&lt;/td&gt;
&lt;td&gt;34.77 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orphaned Chromium processes&lt;/td&gt;
&lt;td&gt;3+ (growing)&lt;/td&gt;
&lt;td&gt;0 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VM RAM usage&lt;/td&gt;
&lt;td&gt;7/8 GB&lt;/td&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker commands failing&lt;/td&gt;
&lt;td&gt;Frequently&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;That's a 10x memory reduction&lt;/strong&gt; from a single &lt;code&gt;finally&lt;/code&gt; block.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use try/catch/finally with Puppeteer — ensures browser cleanup even if an error occurs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not launch one browser per URL — reuse a single instance for multiple pages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set timeouts for goto, waitForSelector, and waitForTimeout — avoids hangs on unreachable hosts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scrape sequentially for many devices — parallel Chromium launches explode memory usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitor with ps aux | grep chromium — if processes accumulate, you have a leak.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🔧 Bonus: Quick Cleanup if You're Already Leaking
&lt;/h2&gt;

&lt;p&gt;If you're in this situation right now and need to free memory immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Kill all orphaned Chromium&lt;/span&gt;
pkill &lt;span class="nt"&gt;-f&lt;/span&gt; chromium

&lt;span class="c"&gt;# Free OS cache (safe)&lt;/span&gt;
&lt;span class="nb"&gt;sudo sync&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;3 | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /proc/sys/vm/drop_caches

&lt;span class="c"&gt;# Add swap as a safety net&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 4G /swapfile
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon /swapfile
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/swapfile none swap sw 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;This approach ensures Puppeteer scrapers are robust, memory-efficient, and production-safe, even when scraping dozens of devices sequentially.&lt;/p&gt;




</description>
      <category>puppeteer</category>
      <category>javascript</category>
      <category>docker</category>
    </item>
    <item>
      <title>Deploy Your Laravel Demo for Free — Domain, MySQL &amp; Hosting at $0</title>
      <dc:creator>Antonios Thanasis</dc:creator>
      <pubDate>Sun, 22 Feb 2026 14:02:11 +0000</pubDate>
      <link>https://dev.to/antoniosthanasisgit/deploy-your-laravel-demo-for-free-domain-mysql-hosting-at-0-3644</link>
      <guid>https://dev.to/antoniosthanasisgit/deploy-your-laravel-demo-for-free-domain-mysql-hosting-at-0-3644</guid>
      <description>&lt;p&gt;If you've built a Laravel project and need a live demo link — for a portfolio, a client presentation, or just to share with friends — spinning up a VPS just to host a demo feels like overkill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InfinityFree&lt;/strong&gt; gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 A free subdomain (&lt;code&gt;yourapp.great-site.net&lt;/code&gt; or &lt;code&gt;.epizy.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;🗄️ A free MySQL database&lt;/li&gt;
&lt;li&gt;☁️ PHP hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All for &lt;strong&gt;$0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The catch? It's not a typical shared host. There are real restrictions that break a standard Laravel deployment out of the box. This post documents exactly what breaks and how to fix every single one of them — based on a real deployment I did for a Laravel social media app.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Know the Limitations First
&lt;/h2&gt;

&lt;p&gt;Before you start, understand what you're working with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Available?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free subdomain&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free MySQL database&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP support&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File Manager (browser-based)&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH access&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;php artisan&lt;/code&gt; commands&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symlinks (&lt;code&gt;storage:link&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;open_basedir&lt;/code&gt; restriction&lt;/td&gt;
&lt;td&gt;❌ PHP only reads inside &lt;code&gt;htdocs/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composer on server&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🔴 &lt;strong&gt;The &lt;code&gt;open_basedir&lt;/code&gt; restriction is the biggest gotcha.&lt;/strong&gt; PHP will refuse to read any file outside of &lt;code&gt;htdocs/&lt;/code&gt; — which completely breaks Laravel's default folder structure.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1 — Prepare Your Project Locally
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Set your .env for production
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_NAME=YourApp
APP_ENV=production
APP_KEY=base64:YOUR_32_CHAR_KEY_HERE==
APP_DEBUG=true   # temporarily true to see errors
APP_URL=http://yourapp.great-site.net

DB_HOST=sql123.epizy.com   # from your cPanel
DB_DATABASE=epiz_xxxxx_dbname
DB_USERNAME=epiz_xxxxx_user
DB_PASSWORD=yourpassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 You can't run &lt;code&gt;php artisan key:generate&lt;/code&gt; on InfinityFree. Generate your &lt;code&gt;APP_KEY&lt;/code&gt; locally and paste it in. It must start with &lt;code&gt;base64:&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Package your files into two ZIPs
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;vendor/&lt;/code&gt; folder is typically 30–50MB which exceeds the File Manager upload limit. Split it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ZIP 1 — everything except vendor/&lt;/span&gt;
zip &lt;span class="nt"&gt;-r&lt;/span&gt; project.zip &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"vendor/*"&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;".git/*"&lt;/span&gt;

&lt;span class="c"&gt;# ZIP 2 — vendor/ folder only&lt;/span&gt;
zip &lt;span class="nt"&gt;-r&lt;/span&gt; vendor.zip vendor/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Upload via File Manager
&lt;/h2&gt;

&lt;p&gt;FTP may not connect reliably on InfinityFree's free tier. Use the built-in &lt;strong&gt;File Manager&lt;/strong&gt; in your cPanel instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to InfinityFree → open &lt;strong&gt;File Manager&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;htdocs/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Upload &lt;code&gt;project.zip&lt;/code&gt; → right-click → &lt;strong&gt;Extract&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Upload &lt;code&gt;vendor.zip&lt;/code&gt; → extract into the same folder&lt;/li&gt;
&lt;li&gt;Upload your &lt;code&gt;.env&lt;/code&gt; file into the project root&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ Everything — and I mean everything — must be inside &lt;code&gt;htdocs/&lt;/code&gt;. Due to &lt;code&gt;open_basedir&lt;/code&gt;, PHP cannot access any path above it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Your final directory structure should look like this:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;htdocs/
├── index.php        ← moved from public/
├── .htaccess        ← moved from public/
├── storage/
│   └── avatars/     ← manually created for uploads
├── app/
├── bootstrap/
├── config/
├── database/
├── resources/
├── routes/
├── vendor/
├── .env
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3 — Fix index.php
&lt;/h2&gt;

&lt;p&gt;Laravel's default &lt;code&gt;public/index.php&lt;/code&gt; references &lt;code&gt;../vendor&lt;/code&gt; and &lt;code&gt;../bootstrap&lt;/code&gt; — paths that go &lt;strong&gt;outside&lt;/strong&gt; &lt;code&gt;htdocs/&lt;/code&gt;. Since everything is now flat inside &lt;code&gt;htdocs/&lt;/code&gt;, you need to remove the &lt;code&gt;../&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the full corrected &lt;code&gt;index.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="c1"&gt;// Temporary — remove once everything works&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'display_errors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'display_startup_errors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;error_reporting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;E_ALL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LARAVEL_START'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;microtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ No more ../ — everything is in htdocs/&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/bootstrap/app.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$kernel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Illuminate\Contracts\Http\Kernel&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="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$kernel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$kernel&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The only change from the default:&lt;/strong&gt; &lt;code&gt;__DIR__.'/../vendor'&lt;/code&gt; → &lt;code&gt;__DIR__.'/vendor'&lt;/code&gt; (same for bootstrap).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Fix File Uploads (No storage:link)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;php artisan storage:link&lt;/code&gt; creates a symlink from &lt;code&gt;public/storage&lt;/code&gt; to &lt;code&gt;storage/app/public&lt;/code&gt;. Symlinks don't work on InfinityFree.&lt;/p&gt;

&lt;p&gt;The fix is to bypass Laravel's storage system for uploads and write directly to the web-accessible folder using &lt;code&gt;$_SERVER['DOCUMENT_ROOT']&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Default Laravel — breaks on InfinityFree&lt;/span&gt;
&lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'avatar'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'avatar'&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;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'avatars'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ InfinityFree-compatible&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'avatar'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'avatar'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getClientOriginalExtension&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$dest&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'DOCUMENT_ROOT'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/storage/avatars'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'avatar'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'avatars/'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$filename&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;Files saved this way are immediately accessible at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yourapp.great-site.net/storage/avatars/filename.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;✅ Make sure the &lt;code&gt;htdocs/storage/avatars/&lt;/code&gt; folder exists before uploading. Create it manually in File Manager.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 5 — Set Up the Database
&lt;/h2&gt;

&lt;p&gt;Since there's no SSH, you can't run &lt;code&gt;php artisan migrate&lt;/code&gt;. Export your schema locally and import it via phpMyAdmin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Export locally&lt;/span&gt;
mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; your_local_db &lt;span class="nt"&gt;--no-data&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; schema.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in InfinityFree cPanel:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;MySQL Databases&lt;/strong&gt; → create a database and user&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;phpMyAdmin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your new database → click &lt;strong&gt;Import&lt;/strong&gt; → upload your &lt;code&gt;.sql&lt;/code&gt; file&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Errors You'll Hit &amp;amp; How to Fix Them
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;403 Forbidden&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;index.php&lt;/code&gt; found in &lt;code&gt;htdocs/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move &lt;code&gt;public/&lt;/code&gt; contents into &lt;code&gt;htdocs/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HTTP 500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;APP_KEY&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;APP_KEY&lt;/code&gt; manually starting with &lt;code&gt;base64:&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;open_basedir restriction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Files placed outside &lt;code&gt;htdocs/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move everything inside &lt;code&gt;htdocs/&lt;/code&gt;, fix &lt;code&gt;index.php&lt;/code&gt; paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Images not loading&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;storage:link&lt;/code&gt; not supported&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;DOCUMENT_ROOT&lt;/code&gt; to write uploads directly to &lt;code&gt;htdocs/storage/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ✅ Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Before going live, run through this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] APP_KEY set in .env (starts with base64:...)
[ ] All files inside htdocs/ — nothing outside
[ ] index.php uses __DIR__.'/vendor' not __DIR__.'/../vendor'
[ ] .htaccess moved from public/ to htdocs/
[ ] vendor/ folder uploaded and extracted
[ ] Database schema imported via phpMyAdmin
[ ] htdocs/storage/avatars/ folder created manually
[ ] File upload controller uses $_SERVER['DOCUMENT_ROOT']
[ ] APP_DEBUG set to false when done ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;InfinityFree is not for production. There's no SSH, no queue workers, no scheduler, and performance is limited. But for &lt;strong&gt;demo projects, portfolios, and proof-of-concept apps&lt;/strong&gt; it does exactly what it promises — gets your Laravel app live with a real URL, a real database, and zero cost.&lt;/p&gt;

&lt;p&gt;If you outgrow it, the same codebase deploys cleanly to any proper VPS — just reverse the &lt;code&gt;index.php&lt;/code&gt; paths and re-run &lt;code&gt;php artisan storage:link&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built and deployed a Laravel project on InfinityFree? Drop your experience in the comments — especially if you hit an error not covered here!&lt;/em&gt; 👇&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>deployment</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Queue Runs in Sync Mode Despite QUEUE_CONNECTION=database in .env</title>
      <dc:creator>Antonios Thanasis</dc:creator>
      <pubDate>Fri, 13 Feb 2026 14:03:42 +0000</pubDate>
      <link>https://dev.to/antoniosthanasisgit/my-queue-runs-in-sync-mode-even-though-env-has-queueconnectiondatabase-5ded</link>
      <guid>https://dev.to/antoniosthanasisgit/my-queue-runs-in-sync-mode-even-though-env-has-queueconnectiondatabase-5ded</guid>
      <description>&lt;h2&gt;
  
  
  Queue Runs in Sync Mode Despite &lt;code&gt;QUEUE_CONNECTION=database&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes after switching your queue connection from &lt;code&gt;sync&lt;/code&gt; to &lt;code&gt;database&lt;/code&gt;, jobs still run synchronously — or nothing works as expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The culprit is usually Laravel's configuration cache.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even if your &lt;code&gt;.env&lt;/code&gt; is correct:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Laravel may still be reading a stale cached config.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Fix
&lt;/h2&gt;

&lt;p&gt;Run these two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:clear
php artisan queue:restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;config:clear&lt;/code&gt; — removes the cached configuration so Laravel reads your &lt;code&gt;.env&lt;/code&gt; fresh&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;queue:restart&lt;/code&gt; — signals running queue workers to restart and pick up the new config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, your queue should correctly use the &lt;code&gt;database&lt;/code&gt; driver. 🚀&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Happy coding!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>queue</category>
      <category>debugging</category>
    </item>
  </channel>
</rss>
