<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Samir Patil on Medium]]></title>
        <description><![CDATA[Stories by Samir Patil on Medium]]></description>
        <link>https://medium.com/@samir00?source=rss-39713019f427------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*atn1aCx_fxACuvrjzOxd5w@2x.jpeg</url>
            <title>Stories by Samir Patil on Medium</title>
            <link>https://medium.com/@samir00?source=rss-39713019f427------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 23 Jun 2026 15:45:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@samir00/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Apple Is Quietly Commoditizing AI Infrastructure]]></title>
            <link>https://samir00.medium.com/apple-is-quietly-commoditizing-ai-infrastructure-6c448d639985?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/6c448d639985</guid>
            <category><![CDATA[local-llm]]></category>
            <category><![CDATA[mlx]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Wed, 10 Jun 2026 10:24:19 GMT</pubDate>
            <atom:updated>2026-06-10T10:54:24.310Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aJpeBGbfEcUEBR2Qyyyr2g.png" /></figure><p>Most people watching WWDC focused on Apple Intelligence.</p><p>Some focused on the new M5 chips.</p><p>Others noticed MLX Swift and Apple’s new distributed machine learning capabilities.</p><p>I think they’re all looking at the wrong thing.</p><p>The most important AI announcement at WWDC wasn’t a model or an assistant.</p><p>It was infrastructure.</p><p>For the last few years, AI has been controlled by three scarce resources:</p><ul><li>Access to frontier models</li><li>Access to GPU infrastructure</li><li>Access to the capital required to operate both</li></ul><p>The first barrier is already falling.</p><p>Open-source models like DeepSeek, Qwen, and Llama have shown that powerful AI is no longer exclusive to a handful of companies. Every few months, a new model emerges that closes the gap between open and closed systems.</p><p>But while intelligence itself is becoming increasingly accessible, the infrastructure required to run it remains expensive and centralized.</p><p>If a startup wanted to deploy a powerful model, the default answer was almost always the same:</p><ol><li>Rent GPUs.</li><li>Use a cloud provider.</li><li>Pay an API vendor.</li></ol><p>That is why Apple’s announcements around MLX matter far more than they initially appear.</p><p>Taken individually, features like MLX Swift, distributed inference, distributed training, RDMA over Thunderbolt, and MLX Distributed look like incremental engineering improvements.</p><p>Taken together, they look like a complete AI stack.</p><p>The narrative around local AI has traditionally been about shrinking models so they can fit on a laptop.</p><p>Apple is pursuing that path too.</p><p>But Apple’s distributed MLX announcements reveal a second path: instead of being constrained by the memory of a single machine, developers can distribute workloads across multiple Apple Silicon systems and run models that would otherwise be impossible on a single device.</p><p>Imagine a 30-person startup where every employee uses a Mac. Today, those machines are clients. Tomorrow, they could collectively become part of the company’s AI infrastructure.</p><p>Not necessarily replacing hyperscale cloud deployments, but potentially providing enough aggregate compute and memory to host powerful open-source models, internal copilots, knowledge systems, and AI workflows that previously required dedicated GPU resources.</p><p>Apple isn’t replacing the cloud.</p><p>And it isn’t replacing small local models.</p><p>What it is doing is expanding the range of what can be run on hardware that organizations already own.</p><p>The most interesting part is what happens when the next DeepSeek-level breakthrough arrives.</p><p>Instead of waiting for a cloud provider or API vendor to make it available, companies may be able to deploy it themselves on infrastructure they already have.</p><p>Open-source models commoditized intelligence.</p><p><strong>Apple’s MLX stack may be the first serious attempt to commoditize the infrastructure required to run it.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6c448d639985" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Hidden HTTPS Bug Behind Load Balancers]]></title>
            <link>https://samir00.medium.com/the-hidden-https-bug-behind-load-balancers-32abdce08231?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/32abdce08231</guid>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[cloud-infrastructure]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Sun, 24 May 2026 19:56:32 GMT</pubDate>
            <atom:updated>2026-05-24T19:56:32.341Z</atom:updated>
            <content:encoded><![CDATA[<h3>Why Your App Breaks Behind a Load Balancer</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*29CnV3yI8EmjIOJsICsz8g.png" /></figure><p>You deploy your app.</p><p>HTTPS is enabled.<br>The SSL certificate works.<br>Everything looks perfect.</p><p>Then suddenly:</p><blockquote><strong><em>“Mixed Content: The page at ‘</em></strong><a href="https://yourdomain.com"><strong><em>https://yourdomain.com</em></strong></a><strong><em>&#39; was loaded over HTTPS, but requested an insecure resource ‘</em></strong><a href="http://yourdomain.com/api/"><strong><em>http://yourdomain.com/api/</em></strong></a><strong><em>...&#39; This request has been blocked.”</em></strong></blockquote><p>Or you get a redirect loop. Or your API calls silently fail.</p><p>You check your load balancer — HTTPS is configured correctly. You check Nginx — it’s running. You check your backend — it’s healthy.</p><p>So what went wrong?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7bJwM5nan1L106ADLLms1w.png" /></figure><p>The issue usually isn’t your load balancer.<br>It isn’t Nginx either.</p><p>Your backend simply doesn’t know the original request was HTTPS.</p><h3>The Architecture Most Apps Use</h3><pre>Browser → HTTPS → Load Balancer<br>                     ↓ HTTP<br>                  Nginx / Proxy<br>                     ↓ HTTP<br>                   Backend</pre><p>This setup is called <strong>SSL termination</strong>.</p><p>The load balancer handles HTTPS, while internal traffic stays on HTTP inside a private network.</p><p>This is completely normal in production.</p><h3>Why Mixed Content Happens</h3><p>Your browser sends:</p><pre>https://yourapp.com</pre><p>The load balancer forwards it internally as HTTP.</p><p>Now your backend thinks:</p><pre>&quot;This is an HTTP request&quot;</pre><p>So when it generates redirects or absolute URLs, it returns:</p><pre>http://yourapp.com/login</pre><p>The browser blocks it because the original page was loaded over HTTPS.</p><p>That’s the entire bug.</p><h3>The Wrong Fix</h3><p>Many engineers solve this by enabling HTTPS everywhere internally too:</p><pre>Browser → HTTPS → Load Balancer → HTTPS → Nginx</pre><p>It works.</p><p>But now you manage certificates in two places and add unnecessary TLS overhead internally.</p><p>You fixed the symptom, not the root cause.</p><h3>The Real Fix: X-Forwarded-Proto</h3><p>Load balancers send a header like this:</p><pre>X-Forwarded-Proto: https</pre><p>This tells your backend:</p><blockquote><em>“The original client request was HTTPS.”</em></blockquote><h3>Nginx</h3><pre>proxy_set_header X-Forwarded-Proto $scheme;</pre><h3>Express / NestJS</h3><pre>app.set(&#39;trust proxy&#39;, 1)</pre><h3>Django</h3><pre>SECURE_PROXY_SSL_HEADER = (&#39;HTTP_X_FORWARDED_PROTO&#39;, &#39;https&#39;)</pre><h3>FastAPI</h3><p>Enable trusted proxy headers so redirects and generated URLs use HTTPS correctly.</p><h3>Final Architecture</h3><pre>Browser (HTTPS)<br>      ↓<br>Load Balancer<br>      ↓<br>X-Forwarded-Proto: https<br>      ↓<br>Nginx<br>      ↓<br>Backend</pre><p>Now your backend correctly generates:</p><pre>https://yourapp.com</pre><p>No mixed content.<br>No redirect loops.<br>No duplicate TLS setup.</p><h3>TL;DR</h3><p>SSL termination at the load balancer is the correct production setup.</p><p>Mixed content errors happen because your backend thinks requests are HTTP.</p><p>The proper fix is:</p><ul><li>pass X-Forwarded-Proto</li><li>trust proxy headers in your framework</li><li>let the backend know the original request was HTTPS</li></ul><p>One header.<br>One configuration.<br>Problem solved.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=32abdce08231" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Problems Users Won’t Tell You About]]></title>
            <link>https://samir00.medium.com/the-problems-users-wont-tell-you-about-a97c59804a16?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/a97c59804a16</guid>
            <category><![CDATA[founder-lessons]]></category>
            <category><![CDATA[product-design]]></category>
            <category><![CDATA[friction-ux]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Wed, 18 Mar 2026 13:22:13 GMT</pubDate>
            <atom:updated>2026-03-18T13:22:13.106Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*K7d-9U3FcZDImkjg" /><figcaption>Photo by <a href="https://unsplash.com/@tinaflour?utm_source=medium&amp;utm_medium=referral">Kristina Flour</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When WhisperFlow started, the founders were trying to build something extremely ambitious: a system that could convert thoughts directly into text. No speaking. No typing. Just thinking.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qieVC2MOJcm_dUARnzeUOA.png" /></figure><p>It sounded like science fiction.</p><p>They spent almost two years trying to make it work. Eventually they couldn’t.</p><p>So they stepped back and asked a different question:</p><p><strong>What is the smallest real problem we can solve right now?</strong></p><p>The answer was surprisingly simple:</p><p><strong>Press Fn → speak → text appears anywhere.</strong></p><p>At first glance this almost looks like giving up on the original vision. From mind-reading technology to just… pressing a key and talking.</p><p>Anyone could criticize this:</p><p><em>“Why build this? We already have ChatGPT. We already have transcription. Just open an app, record, copy, paste.”</em></p><p>Technically correct.</p><p>Product-wise, completely wrong.</p><p>Because the real problem was never transcription.</p><p>It was <strong>friction</strong>.</p><p>Opening another app.<br>Switching tabs.<br>Copying text.<br>Returning back.<br>Rebuilding mental context.</p><p>Each step feels small. Together they destroy flow.</p><p>WhisperFlow didn’t invent a new capability.</p><p>They removed interruption.</p><p>And that is often where the real value lives.</p><h3>Users Don’t Tell You Their Real Problems</h3><p>There is another reason this insight is hard to discover:</p><p><strong>Users rarely tell you their real problems.</strong></p><p>Not because they are hiding them intentionally.</p><p>But because they want to sound competent.</p><p>When people are interviewed, they describe problems that sound professional:</p><ul><li>Lip sync issues</li><li>Storyboarding taking too long</li><li>Image generation quality</li><li>Missing features</li><li>Technical limitations</li></ul><p>These sound like serious problems.</p><p>What they rarely say:</p><ul><li><em>I lose focus switching tools</em></li><li><em>My workflow is scattered</em></li><li><em>I waste time rebuilding context</em></li><li><em>I get distracted between steps</em></li><li><em>Too many tabs slow me down</em></li></ul><p>Because these sound like personal inefficiencies, not product problems.</p><p>So people describe <strong>impressive problems</strong> instead of <strong>real friction</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EbmUory9oqF1leG1lfXMTw.png" /></figure><h3>The Problem I Only Understood After Living It</h3><p>I understood this when I started building AI films for myself.</p><p>Before that, after talking to 20–30 filmmakers, I thought the main problems were:</p><ul><li>Storyboarding</li><li>Lip sync</li><li>Sound design</li><li>Asset reuse</li></ul><p>But when I actually did the work myself, I discovered my real bottleneck wasn’t any of these.</p><p>It was this:</p><p><strong>Context switching.</strong></p><p>My workflow looked like:</p><p>Script in one doc.<br> Storyboard in another.<br> Sound references somewhere else.<br> GPT for rewriting.<br> Claude for refinement.<br> Video tools for generation.<br> Audio tools for voice.</p><p>Constant switching.<br>Constant copying.<br>Constant mental resets.</p><p>Work that should have taken one hour took five or six.</p><p>Not because the work was hard.</p><p>Because the workflow was fragmented.</p><p><strong>And here is the important part:</strong></p><p>Nobody told me this problem.</p><p>I only understood it after experiencing the pain myself.</p><p>Because if someone had asked me earlier, I probably would not have said:</p><p><em>“My biggest problem is switching tabs.”</em></p><p>That doesn’t sound like a serious problem.</p><p>But living it showed me the truth:</p><p><strong>This wasn’t a discipline problem.<br>This was a design problem.</strong></p><h3>The Founder Pattern Most People Miss</h3><p>Here’s the pattern:</p><p><strong>Users describe what sounds important.<br>But they suffer from what happens repeatedly.</strong></p><p>A 2-minute friction repeated 50 times a day is a bigger problem than a 30-minute problem that happens once a week.</p><p>This is where many startup opportunities hide.</p><p>Not in big obvious problems.</p><p>But in small repeated interruptions.</p><p>The best founders don’t just listen to what users say.</p><p>They notice:</p><ul><li>Where energy drops</li><li>Where attention breaks</li><li>Where steps repeat</li><li>Where context resets</li><li>Where flow dies</li></ul><p>Because productivity is rarely destroyed by difficulty. It is usually destroyed by interruption.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Uv9yBo_9HZQ9jAW_735LOA.png" /></figure><h3>The Real Lesson</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jbAYPHU9G4lJfOIlkViNjQ.png" /></figure><p><strong>Don’t just interview users. Become one.</strong></p><p>Because:</p><p>Users explain impressive problems.<br>But experience reveals expensive ones.</p><p>And sometimes the most valuable companies are built not by solving the biggest problems…</p><p>…but by removing the smallest frictions that nobody thinks are worth mentioning.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a97c59804a16" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop the Silent Bloat: Why LangChain Checkpoints Make Your DB 10x Bigger (and How to Clean It)]]]></title>
            <link>https://samir00.medium.com/langchain-database-cleanup-a-production-must-have-51f82a40096d?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/51f82a40096d</guid>
            <category><![CDATA[langchain]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Tue, 06 Jan 2026 19:54:27 GMT</pubDate>
            <atom:updated>2026-01-08T19:06:17.728Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Q414LS4nAvQ-vaTx" /><figcaption>Photo by <a href="https://unsplash.com/@hdbernd?utm_source=medium&amp;utm_medium=referral">Bernd 📷 Dittrich</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>If you are using LangChain in production, one issue will surface sooner or later: the <strong>database size grows much faster than expected</strong>.</p><p>Your actual business data might be only <strong>40–50 MB</strong>, but your database can easily grow to <strong>700–800 MB or more</strong>. This growth is not caused by your core application data — it comes from <strong>LangChain’s checkpointing and state management</strong>.</p><p>This article explains <strong>why this happens</strong>, <strong>which tables are responsible</strong>, and <strong>how to safely clean them up</strong> using a simple, production‑ready cleanup function.</p><p><strong>Why LangChain Databases Grow So Fast</strong></p><p>LangChain maintains execution state to support:</p><ul><li>Stateful agents</li><li>Long‑running workflows</li><li>Recovery from failures</li><li>Tool execution history</li></ul><p>To enable this, LangChain (and LangGraph‑style workflows) writes data into multiple internal tables on <strong>every agent step</strong>.</p><p>Over time, this state data accumulates rapidly — especially when:</p><ul><li>You run multiple agents</li><li>You retry executions</li><li>You scale horizontally</li><li>You do not clean up old threads</li></ul><p><strong>Tables Responsible for Database Bloat</strong></p><p>Most of the unnecessary database growth comes from these tables:</p><ul><li><strong>checkpoints</strong> — Stores serialised agent state as JSON (including timestamps)</li><li><strong>checkpoint_blobs</strong> — Stores large binary or serialised payloads</li><li><strong>checkpoint_writes</strong> — Stores intermediate writes during execution</li></ul><p>These tables grow continuously and <strong>are not automatically cleaned up</strong> by LangChain.</p><p><strong>Why Cleanup Is Mandatory in Production</strong></p><p>If you don’t clean old checkpoints:</p><ul><li>Database size grows indefinitely</li><li>Indexes become bloated</li><li>Queries slow down</li><li>Backups become larger and slower</li><li>Storage costs silently increase</li></ul><p>In many real systems, <strong>90%+ of database size is just expired checkpoint data</strong>.</p><p><strong>Cleanup Strategy</strong></p><p>The safest cleanup strategy is:</p><p><strong>Delete threads whose latest checkpoint is older than a defined retention period (for example, 30 days).</strong></p><p>This ensures:</p><ul><li>Active threads remain untouched</li><li>Only inactive or abandoned agent runs are removed</li><li>No corruption of running workflows</li></ul><p><strong>Step 1: Identify and Clean Old Threads</strong></p><p>Below is a <strong>single, production‑ready function</strong> that:</p><ul><li>Finds all expired thread_ids</li><li>Deletes related data from:</li><li>checkpoints</li><li>checkpoint_blobs</li><li>checkpoint_writes</li><li>Runs safely in one transaction</li></ul><p><strong>Cleanup Function</strong></p><pre>from datetime import datetime, timedelta, timezone<br>import logging<br><br><br>def cleanup_old_threads(checkpointer, retention_days: int) -&gt; None:<br>    &quot;&quot;&quot;<br>    Deletes all threads whose latest checkpoint is older than the given retention period.<br>    This removes data from:<br>      - checkpoints<br>      - checkpoint_blobs<br>      - checkpoint_writes<br>    &quot;&quot;&quot;<br><br>    logging.info(<br>        f&quot;Running checkpoint cleanup for threads older than {retention_days} days...&quot;<br>    )<br><br>    cutoff_date = (<br>        datetime.now(timezone.utc) - timedelta(days=retention_days)<br>    ).isoformat()<br><br>    # Find thread_ids whose latest checkpoint timestamp is older than cutoff<br>    query = &quot;&quot;&quot;<br>        SELECT thread_id<br>        FROM checkpoints<br>        GROUP BY thread_id<br>        HAVING MAX(checkpoint -&gt;&gt; &#39;ts&#39;) &lt; %s<br>    &quot;&quot;&quot;<br><br>    try:<br>        with checkpointer.conn.connection() as conn:<br>            with conn.cursor() as cur:<br>                cur.execute(query, (cutoff_date,))<br>                thread_ids = [row[0] for row in cur.fetchall()]<br><br>                if not thread_ids:<br>                    logging.info(&quot;No expired threads found.&quot;)<br>                    return<br><br>                logging.info(f&quot;Found {len(thread_ids)} expired threads. Deleting...&quot;)<br><br>                # Bulk delete all related data<br>                cur.execute(<br>                    &quot;DELETE FROM checkpoints WHERE thread_id = ANY(%s)&quot;,<br>                    (thread_ids,),<br>                )<br>                cur.execute(<br>                    &quot;DELETE FROM checkpoint_blobs WHERE thread_id = ANY(%s)&quot;,<br>                    (thread_ids,),<br>                )<br>                cur.execute(<br>                    &quot;DELETE FROM checkpoint_writes WHERE thread_id = ANY(%s)&quot;,<br>                    (thread_ids,),<br>                )<br><br>        logging.info(f&quot;Successfully deleted {len(thread_ids)} expired threads.&quot;)<br><br>    except Exception as e:<br>        logging.error(f&quot;Checkpoint cleanup failed: {e}&quot;)</pre><p><strong>Operational Best Practices</strong></p><ul><li>Run this cleanup as a <strong>cron job</strong> (daily or weekly)</li><li>Start with <strong>30‑day retention</strong>, then tune</li><li>Monitor database size before and after cleanup</li><li>Test on staging before running in production</li></ul><p><strong>Psql Query</strong></p><pre>WITH expired_threads AS (<br>    SELECT thread_id<br>    FROM checkpoints<br>    GROUP BY thread_id<br>    HAVING MAX((checkpoint -&gt;&gt; &#39;ts&#39;)::timestamp) &lt; NOW() - INTERVAL &#39;7 days&#39;<br>),<br>del_checkpoints AS (<br>    DELETE FROM checkpoints <br>    WHERE thread_id IN (SELECT thread_id FROM expired_threads)<br>),<br>del_blobs AS (<br>    DELETE FROM checkpoint_blobs <br>    WHERE thread_id IN (SELECT thread_id FROM expired_threads)<br>)<br>DELETE FROM checkpoint_writes <br>WHERE thread_id IN (SELECT thread_id FROM expired_threads);</pre><p><strong>Reclaim space (WARNING: This locks the tables!)</strong></p><pre>VACUUM FULL checkpoint_writes; <br>VACUUM FULL checkpoint_blobs;<br>VACUUM FULL checkpoints;</pre><p><strong>Final Thoughts</strong></p><p>LangChain is extremely powerful, but <strong>stateful agent systems require maintenance</strong>.</p><p>If you are using checkpoints in production and not cleaning them up:</p><ul><li>You are paying a hidden storage tax</li><li>Your database is doing unnecessary work</li><li>Scaling becomes harder over time</li></ul><p>A simple cleanup job like this can <strong>reduce database size by 80–90%</strong> and immediately improve performance.</p><p>If LangChain is running in production, <strong>checkpoint cleanup is not optional it’s mandatory</strong>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=51f82a40096d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Subtle JWT Issue That Broke Our On-Prem Deployment (And Took Half a Day to Debug)]]></title>
            <link>https://samir00.medium.com/the-subtle-jwt-issue-that-broke-our-on-prem-deployment-and-took-half-a-day-to-debug-fa863704fd58?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/fa863704fd58</guid>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[keycloak]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[microservices]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Wed, 10 Dec 2025 12:08:47 GMT</pubDate>
            <atom:updated>2025-12-10T12:08:47.089Z</atom:updated>
            <content:encoded><![CDATA[<p>We recently hit an unexpectedly tricky JWT authentication problem during an on-prem deployment for one of our enterprise customers. On paper, the setup was simple:</p><ul><li><strong>Service A</strong> → Generates JWTs by talking to <strong>Keycloak</strong></li><li><strong>Service B</strong> → Validates those JWTs, again using <strong>Keycloak</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dbkBh76GushoN6R8" /><figcaption>Photo by <a href="https://unsplash.com/@jjik_da?utm_source=medium&amp;utm_medium=referral">Chanhee Lee</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>All three components — <strong>Service A, Service B, and Keycloak </strong>were running on the <em>same machine</em>. Nothing exotic. No networking gymnastics. And yet, every token validation attempt was failing.</p><p>The root cause turned out to be one of those “obvious only after you know it” bugs.</p><h4>🔍 The Setup: Same Machine, Different URLs</h4><p>Even though everything was deployed on a single server, each service reached Keycloak using a <strong>different base URL</strong>:</p><ul><li>http://172.17.0.1:&lt;port&gt;/auth</li><li>http://localhost:&lt;port&gt;/auth</li><li>http://keycloak.mycompany.local:&lt;port&gt;/auth</li></ul><p>All of these pointed to the <em>same</em> Keycloak instance.</p><p>So why should it matter?</p><p>Because it turns out: <strong>the host Keycloak uses when issuing a token must match the host used when validating it</strong>.</p><h4>💥 The Breakage: Mismatched Issuer Hosts</h4><p>When Service A requested a JWT from Keycloak, the token included metadata — most importantly, the issuer (the iss claim). That issuer is tied to the <strong>exact base URL</strong> Keycloak sees during token generation.</p><p>So if Service A generated a token using:</p><pre>http://172.17.0.1:&lt;port&gt;/auth</pre><p>Then Service B <em>must</em> validate the token using the <strong>same exact URL</strong>.</p><p>But in our case, Services A and B were using different hosts to talk to Keycloak. And that tiny mismatch meant:</p><ul><li>The iss claim inside the token didn’t match</li><li>Keycloak rejected the token during validation</li><li>Every validation attempt failed, even though the token itself was perfectly valid</li></ul><p>No obvious errors — just consistent validation failures.</p><h4>🧠 The “Aha!” Moment</h4><p>Looking back, it feels like a simple configuration oversight.</p><p>But during debugging — between unclear documentation, container networking, and the complexity of on-prem environments — this tiny inconsistency took <strong>half a day</strong> to uncover.</p><p>Once we aligned all services to use the <strong>same Keycloak base URL</strong>, everything started working instantly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O1Vccb0avnNQbHDyNSiDLg.png" /></figure><h4>✅ Takeaway: Consistency Matters (More Than You Think)</h4><p>If you’re using Keycloak (or any identity provider that embeds issuer metadata), make sure:</p><ul><li>All services use the <strong>same base URL</strong> to interact with the auth server</li><li>Avoid mixing localhost, IPs, and hostnames</li><li>Ensure container-internal URLs match whatever will be used for validation</li></ul><p>It’s a small detail, but it can completely break your authentication flow in ways that are not immediately obvious.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa863704fd58" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solving the Silent Postgres Disconnect Problem in Agentic Systems]]></title>
            <link>https://samir00.medium.com/production-proof-persistence-auto-reconnect-postgres-for-langgraph-33dc85b3f4b1?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/33dc85b3f4b1</guid>
            <category><![CDATA[langgraph]]></category>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[langchain]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Sat, 15 Nov 2025 17:58:52 GMT</pubDate>
            <atom:updated>2025-11-20T06:36:47.619Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>How I Fixed Idle Connection Failures in Production</strong></p><p>Long-running agent workflows are one of LangGraph’s biggest strengths. But they also expose a subtle production issue: <strong>PostgreSQL quietly kills idle connections</strong>, and your persistence layer isn’t always ready for it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*49VdOm5QfHYpHWJa" /><figcaption>Photo by <a href="https://unsplash.com/@keringedge?utm_source=medium&amp;utm_medium=referral">Kerin Gedge</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>If your app holds a connection for too long and Postgres restarts, times it out, or drops it, you won’t find out until the next query suddenly fails. For agent systems that depend on continuous checkpointing, that failure can stall runs or even require a server restart.</p><p>I ran into this exact issue. Here’s what was happening and the solution that made my persistence layer production-proof.</p><h3>The Problem: Idle Connections Die in Production</h3><p>In development, your database rarely disconnects. But in real deployments, several things can kill idle sessions:</p><ul><li>Postgres maintenance restarts</li><li><strong>idle_session_timeout</strong> or PgBouncer timeouts</li><li>Short network blips or failovers</li></ul><p>When your persistence layer tries to reuse one of these dead connections, the driver throws an error like:</p><pre>connection not open<br>server closed the connection unexpectedly</pre><p>Without explicit recovery, that error bubbles up and breaks checkpointing logic. Some setups even end up needing a full restart to reset connections.</p><p>That’s unacceptable for agentic systems running for hours — or days.</p><h3>What Reliability Should Look Like</h3><p>A resilient persistence layer should:</p><ul><li>Detect retry-able connection errors</li><li>Reconnect automatically</li><li>Retry the failed operation</li><li>Do all of this transparently, without changing the rest of your code</li></ul><p>Surprisingly, this behavior isn’t built into most savers by default.</p><h3>The Fix:</h3><h3>ResilientPostgresSaver</h3><p>To solve this, I built a wrapper: <strong>ResilientPostgresSaver</strong>.</p><p>It sits around the existing Postgres saver and adds reconnection + retry logic in a clean, centralized way.</p><h3>Key Features</h3><p><strong>1. Automatic Connection Error Detection</strong></p><p>The saver intercepts errors associated with terminated or invalid connections. Instead of failing fast, it attempts controlled recovery.</p><p><strong>2. Retry Logic with Backoff</strong></p><p>You can set:</p><ul><li>max_retries</li><li>retry_delay</li></ul><p>This makes the retry flow predictable and production-safe.</p><p><strong>3. Works With Connection Pools</strong></p><p>Instead of destroying the pool, it simply acquires a fresh connection.</p><p>This avoids double-closing and keeps pool state healthy.</p><p><strong>4. Drop-In Replacement</strong></p><p>You don’t have to touch anything else in your LangGraph code.</p><p><strong>5. Survives Forced Termination (pg_backend_terminate)</strong></p><p>Even if a backend connection is explicitly terminated (for example, using pg_backend_terminate), the saver detects the failure, acquires a fresh connection, and retries, so workflows keep running uninterrupted.</p><h3>Usage Example</h3><pre>conn_pool = get_connection_pool(database_url)<br>checkpointer = ResilientPostgresSaver(<br>    conn_pool,<br>    max_retries=3,<br>    retry_delay=2.0<br>)<br>checkpointer.setup()</pre><p>If the connection was idle and Postgres restarts, the saver:</p><ol><li>Catches the failure</li><li>Reconnects</li><li>Retries the operation</li></ol><p>Your agent continues running without interruptions.</p><h3>Impact</h3><p>After integrating this, I saw immediate improvements:</p><ul><li><strong>Zero crashes</strong> during brief database restarts</li><li><strong>Stable agent runs</strong> even with long idle periods</li><li><strong>No more manual process restarts</strong> after connection resets</li></ul><p>It turned out to be one of those small architectural upgrades that significantly improves reliability.</p><h3>Final Thoughts</h3><p>Postgres is reliable, but production environments aren’t perfect. Idle connections can and will be dropped. If you’re running LangGraph with Postgres, your persistence layer needs to handle that reality.</p><p><strong>ResilientPostgresSaver</strong> ensures that transient database hiccups don’t break your workflows.</p><p>I have used this in my agentic repo template : <a href="https://github.com/samirpatil2000/agentic-template/blob/main/agents/resilient_postgres_saver.py">https://github.com/samirpatil2000/agentic-template/blob/main/agents/resilient_postgres_saver.py</a></p><p>Code Snippet</p><pre>import time<br>from typing import Optional<br>import logging<br><br>from langgraph.checkpoint.postgres import Conn, PostgresSaver<br>from langgraph.checkpoint.serde.base import SerializerProtocol<br>from psycopg import Connection, Pipeline<br>from psycopg.errors import OperationalError<br>from psycopg.rows import dict_row<br>from psycopg_pool import ConnectionPool<br><br># Configure logger<br>logger = logging.getLogger(__name__)<br><br><br>class ResilientPostgresSaver(PostgresSaver):<br>    def __init__(<br>        self,<br>        conn: Conn,<br>        pipe: Optional[Pipeline] = None,<br>        serde: Optional[SerializerProtocol] = None,<br>        max_retries: int = 3,<br>        retry_delay: float = 2.0,  # seconds<br>    ) -&gt; None:<br>        super().__init__(conn, pipe, serde)<br>        self.max_retries = max_retries<br>        self.retry_delay = retry_delay<br><br>    def _execute_with_retries(self, query_func, *args, **kwargs):<br>        retries = 0<br>        while retries &lt; self.max_retries:<br>            try:<br>                return query_func(*args, **kwargs)<br>            except (OperationalError, ConnectionError) as e:<br>                logging.error(<br>                    f&quot;Database operation failed: {e}, retrying...&quot;, exc_info=True<br>                )<br>                retries += 1<br>                if retries &gt;= self.max_retries:<br>                    raise<br>                time.sleep(self.retry_delay)<br>                self._reconnect()<br><br>    def _reconnect(self):<br>        try:<br>            self.conn.close()<br>        except Exception as e:<br>            logging.exception(f&quot;Error closing connection {e}&quot;, exc_info=True)<br><br>        if isinstance(self.conn, ConnectionPool):<br>            logging.info(f&quot;Reinitializing connection pool for {self.conn.conninfo}&quot;)<br>            try:<br>                self.conn = get_connection_pool(self.conn.conninfo)<br>            except Exception as e:<br>                logging.exception(<br>                    f&quot;Error reinitializing connection pool {e}&quot;, exc_info=True<br>                )<br>        else:<br>            try:<br>                self.conn = Connection.connect(<br>                    self.conn.conninfo,<br>                    autocommit=True,<br>                    prepare_threshold=0,<br>                    row_factory=dict_row,<br>                )<br>            except Exception as e:<br>                logging.exception(f&quot;Error reconnecting {e}&quot;, exc_info=True)<br><br>    def setup(self) -&gt; None:<br>        self._execute_with_retries(super().setup)<br><br>    def list(self, *args, **kwargs):<br>        return self._execute_with_retries(super().list, *args, **kwargs)<br><br>    def get_tuple(self, *args, **kwargs):<br>        return self._execute_with_retries(super().get_tuple, *args, **kwargs)<br><br>    def put(self, *args, **kwargs):<br>        return self._execute_with_retries(super().put, *args, **kwargs)<br><br>    def put_writes(self, *args, **kwargs):<br>        return self._execute_with_retries(super().put_writes, *args, **kwargs)<br><br><br>def get_connection_pool(db_url: str):<br>    connection_kwargs = {<br>        &quot;autocommit&quot;: True,<br>        &quot;prepare_threshold&quot;: 0,<br>        &quot;application_name&quot;: &quot;cosmos&quot;,<br>    }<br><br>    conn_pool = ConnectionPool(<br>        conninfo=db_url,<br>        kwargs=connection_kwargs,<br>        min_size=1,<br>        max_size=4,<br>        max_idle=60 * 2,<br>    )<br>    return conn_pool</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=33dc85b3f4b1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build Agentic AI Systems Faster with a FastAPI + LangGraph Workflow Template]]></title>
            <link>https://samir00.medium.com/build-agentic-ai-systems-faster-with-a-fastapi-langgraph-workflow-template-e163650e5cf1?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/e163650e5cf1</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[langgraph]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Tue, 11 Nov 2025 13:43:15 GMT</pubDate>
            <atom:updated>2025-11-11T13:43:15.839Z</atom:updated>
            <content:encoded><![CDATA[<p>I just shipped an open-source template for orchestrating agentic AI systems — built on FastAPI and LangGraph — designed to help you go from idea to production-ready workflows, quickly and cleanly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iVpJ9mFLKfjpi80J" /><figcaption>Photo by <a href="https://unsplash.com/@lunarts?utm_source=medium&amp;utm_medium=referral">Volodymyr Hryshchenko</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>What’s inside</p><ul><li>FastAPI REST endpoints to start and continue workflows</li><li>LangGraph-powered orchestration with modular nodes</li><li>State management, checkpointing, and PostgreSQL persistence</li><li>Non-blocking execution via ThreadPoolExecutor</li><li>A clean, extensible architecture that’s easy to grow</li><li>And more</li></ul><p>Why it helps</p><ul><li>Kickstart agent workflows with a sensible baseline</li><li>Get built-in persistence plus interrupts/continuations</li><li>Scale with a clear, maintainable structure</li></ul><p>Who it’s for</p><ul><li>Teams prototyping agentic systems</li><li>Developers who need reliable state + orchestration from day one</li><li>Anyone tired of stitching together ad hoc workflow plumbing</li></ul><p>I’m actively improving the template and would love your feedback. Raise an issue or open a PR to add features, fix rough edges, or suggest patterns.</p><p>Repo link: <a href="https://github.com/samirpatil2000/agentic-template">https://github.com/samirpatil2000/agentic-template</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e163650e5cf1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Wasting Storage: A Simple Way to Auto-Cleanup Orphaned Files in Your Notes, Comments Editor]]></title>
            <link>https://samir00.medium.com/stop-wasting-storage-a-simple-way-to-auto-cleanup-orphaned-files-in-your-notes-comments-editor-218b90a6338b?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/218b90a6338b</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[s3]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Wed, 24 Sep 2025 19:46:30 GMT</pubDate>
            <atom:updated>2025-09-24T19:46:30.275Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eA2XLCoK5FGs2nmn" /><figcaption>Photo by <a href="https://unsplash.com/@leiadakrozjhen?utm_source=medium&amp;utm_medium=referral">Leiada Krözjhen</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When you’re building a document editor with features like <strong>auto-save</strong> and <strong>file uploads</strong> (images, PDFs, etc.), one sneaky problem often creeps in:</p><blockquote><em>Files stay in storage even when the user deletes them from the document — leading to wasted space and higher costs over time.</em></blockquote><p>Let’s break this down and see how we can solve it once and for all.</p><h3>The Problem</h3><p>Imagine this workflow:</p><ol><li>A user uploads an image.</li><li>Your frontend immediately uploads the file to blob storage (like S3) and inserts its URL into the document content.</li><li>Your app auto-saves the updated content.</li><li>The user changes their mind, deletes the image from the document, and moves on.</li></ol><p>Here’s the catch:</p><p>❌ <strong>The file is still sitting in storage.</strong></p><p>Over time, thousands of such orphaned files can accumulate, bloating your storage bill and making it harder to manage content.</p><h3>✅ The Solution: Delayed Cleanup</h3><p>Instead of trying to clean up files in real-time (which is tricky with auto-save), you can use a <strong>delayed cleanup mechanism</strong>.</p><p>This approach gives users a grace period to undo changes — while keeping your storage clean and lean.</p><p>Here’s the step-by-step strategy:</p><h3>1️⃣ File Upload Handling</h3><p>When a user uploads a file, store it under a <strong>document-scoped path</strong>:</p><pre>/documents/{documentId}/{uniqueFileId}.{extension}</pre><p>This makes it easy to list all files related to a specific document later.</p><p>Immediately insert the file URL into the document’s in-progress content and auto-save.</p><h3>2️⃣ Track Document Updates</h3><p>Every time a document is updated, push a small payload into a queue (or Redis list):</p><pre>{<br>  &quot;documentId&quot;: &quot;12345&quot;,<br>  &quot;lastUpdatedAt&quot;: &quot;2025-09-24T18:12:00Z&quot;<br>}</pre><p>This tells your system that something changed and might need cleanup later.</p><h3>3️⃣ Delayed Cleanup Trigger</h3><p>Have a <strong>background worker</strong> consume this queue.</p><p>For each update:</p><ul><li>Wait for a grace period <strong>debounce</strong> (e.g. 5 minutes).</li><li>If no further edits occur within that time window, consider the document “stable.”</li><li>Trigger the cleanup job.</li></ul><h3>4️⃣ Cleanup Job Steps</h3><p>The cleanup job runs through this sequence:</p><ol><li><strong>Fetch the latest document content</strong> from the database.</li><li><strong>Parse and collect all file URLs</strong> still referenced in the content.</li><li><strong>List all files</strong> under /documents/{documentId}/ in storage.</li><li><strong>Compare both lists</strong> (set difference):</li><li>Files in storage but not in the document = orphaned files.</li><li><strong>Delete orphaned files</strong> from storage.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RD1r8BVAflcsIWI7iQHXCg.png" /></figure><h3>🎯 Benefits of This Approach</h3><p>✅ <strong>Automatic Cleanup</strong> — No manual admin work required.</p><p>✅ <strong>Storage Cost Savings</strong> — Unused files don’t pile up.</p><p>✅ <strong>User-Friendly</strong> — The grace period allows undoing accidental deletes.</p><p>✅ <strong>Consistency</strong> — Storage reflects the true state of your document.</p><h3>🧠 Final Thoughts</h3><p>Building collaborative, auto-saving editors is tricky enough without worrying about rogue files silently eating into your storage budget.</p><p>By introducing a <strong>delayed cleanup process</strong>, you get:</p><ul><li>A clean document-to-file relationship</li><li>Lower storage bills</li></ul><p>This is one of those small but powerful optimizations that make your system more robust at scale.</p><h3>⚠️ Important Note on Versioning</h3><p>If your system uses <strong>document versioning</strong> (e.g. storing a new row in the database for every save), this approach will need some adjustments.</p><p>You have two options:</p><ul><li><strong>Disable cleanup for older versions</strong> if you want to preserve full history (and thus keep all files ever referenced).</li><li><strong>Limit cleanup to the latest version</strong> by only fetching and parsing the most recent version from the database.</li></ul><p>Make sure your cleanup logic respects your versioning strategy otherwise, you risk deleting files that are still referenced in older document versions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=218b90a6338b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Setting Up n8n with PostgreSQL and NGINX (SSL Included)]]></title>
            <link>https://samir00.medium.com/setting-up-n8n-with-postgresql-and-nginx-ssl-included-0da5012a5989?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/0da5012a5989</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[n8n]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Sat, 20 Sep 2025 07:00:29 GMT</pubDate>
            <atom:updated>2025-09-20T07:02:48.433Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*Zr4JVUPiruk_6S1fOnuOOw.png" /></figure><p>If you’re self-hosting <strong>n8n</strong>, running it with a proper database (PostgreSQL) and securing it with <strong>NGINX + SSL</strong> is the way to go.</p><p>This guide walks you through the <strong>exact steps</strong> I used to get a production-grade setup running — no guesswork, just copy, paste, and ship.</p><h3>🛠 Prerequisites</h3><p>Before we dive in, make sure you have:</p><p>✅ <strong>Ubuntu server</strong> with Docker + Docker Compose installed</p><p>✅ <strong>Domain name</strong> — e.g., n8n.your-company.tld pointing to your server</p><p>✅ <strong>SSL certificate</strong> (self-signed or from Let’s Encrypt/your CA)</p><h3>🔧 Step 1: Configure NGINX as Reverse Proxy</h3><p>Let’s start by putting n8n behind NGINX with SSL termination.</p><p>Edit your NGINX site config (/etc/nginx/sites-available/default):</p><pre>server {<br>    listen 443 ssl http2;<br>    client_max_body_size 2G;</pre><pre>    ssl_certificate /etc/nginx/ssl/ssl-bundle.crt;<br>    ssl_certificate_key /etc/nginx/ssl/sandbox_private_key.pem;</pre><pre>    server_name n8n.your-company.tld;</pre><pre>    proxy_read_timeout 300;</pre><pre>    # Security headers<br>    add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains; preload&quot;;<br>    add_header X-XSS-Protection &quot;1; mode=block&quot;;<br>    add_header X-Content-Type-Options nosniff;<br>    add_header Referrer-Policy &quot;strict-origin&quot;;</pre><pre>    location / {<br>        proxy_pass <a href="http://127.0.0.1:5678/;">http://127.0.0.1:5678/;</a></pre><pre>        # WebSocket support (critical for n8n!)<br>        proxy_http_version 1.1;<br>        proxy_set_header Upgrade $http_upgrade;<br>        proxy_set_header Connection &quot;upgrade&quot;;</pre><pre>        proxy_set_header Host $host;<br>        proxy_set_header X-Real-IP $remote_addr;<br>        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br>        proxy_set_header X-Forwarded-Proto $scheme;</pre><pre>        proxy_read_timeout 600s;<br>        proxy_send_timeout 600s;<br>    }<br>}</pre><p>Test &amp; reload:</p><pre>sudo nginx -t &amp;&amp; sudo systemctl restart nginx</pre><h3>Step 2: Environment Variables</h3><p>Create a .env file in your project directory:</p><pre># Database<br>DB_TYPE=postgresdb<br>DB_POSTGRESDB_HOST=postgres<br>DB_POSTGRESDB_PORT=5432<br>DB_POSTGRESDB_DATABASE=n8n<br>DB_POSTGRESDB_USER=postgres<br>DB_POSTGRESDB_PASSWORD=postgres</pre><pre># n8n<br>N8N_BASIC_AUTH_ACTIVE=true<br>N8N_BASIC_AUTH_USER=admin<br>N8N_BASIC_AUTH_PASSWORD=secret</pre><pre>N8N_HOST=n8n.your-company.tld<br>N8N_PROTOCOL=https<br>N8N_PORT=5678</pre><pre>VUE_APP_URL_BASE_API=https://n8n.your-company.tld/</pre><p>💡 <strong>Pro tip:</strong> Use a strong password in production. Consider managing secrets with Vault or Doppler.</p><h3>Step 3: Docker Compose Setup</h3><p>Create docker-compose.yml:</p><pre>version: &#39;3.8&#39;</pre><pre>services:<br>  postgres:<br>    image: postgres:15<br>    restart: always<br>    environment:<br>      POSTGRES_USER: ${DB_POSTGRESDB_USER}<br>      POSTGRES_PASSWORD: ${DB_POSTGRESDB_PASSWORD}<br>      POSTGRES_DB: ${DB_POSTGRESDB_DATABASE}<br>    volumes:<br>      - postgres_data:/var/lib/postgresql/data<br>    ports:<br>      - &quot;5432:5432&quot; # Optional — only if you need external access</pre><pre>  n8n:<br>    image: n8nio/n8n:1.111.0<br>    restart: always<br>    ports:<br>      - &quot;5678:5678&quot;<br>    env_file:<br>      - .env<br>    depends_on:<br>      - postgres<br>    volumes:<br>      - ./n8n_data:/home/node/.n8n</pre><pre>volumes:<br>  postgres_data:<br>  n8n_data:</pre><p>Bring everything up:</p><pre>docker-compose up -d</pre><p>Check logs to ensure n8n is running properly:</p><pre>docker-compose logs -f n8n</pre><h3>Step 4: Access Your Instance</h3><p>Once everything is running, open:</p><p><strong>➡️ </strong><a href="https://n8n.your-company.tld"><strong>https://n8n.your-company.tld</strong></a></p><p>Login with:</p><ul><li><strong>Username:</strong> admin</li><li><strong>Password:</strong> secret</li></ul><h3>💡 Pro Tips for Production</h3><p>🔒 Use <strong>Let’s Encrypt</strong> for free, auto-renewing SSL</p><p>🔑 Rotate your N8N_BASIC_AUTH_PASSWORD regularly</p><p>🧱 Ensure your firewall allows <strong>80/443</strong></p><p>⚡ Keep WebSocket headers in NGINX — n8n won’t work properly without them</p><p>By following these steps, you now have a <strong>secure, scalable, production-ready n8n setup</strong> with PostgreSQL as a persistent database and NGINX handling SSL and reverse proxying.</p><p>Here is github repo : <a href="https://github.com/samirpatil2000/n8n">https://github.com/samirpatil2000/n8n</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0da5012a5989" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LLM Mocks: Your New Dev Shortcut]]></title>
            <link>https://samir00.medium.com/llm-mocks-your-new-dev-shortcut-bc9ee9c9f8b7?source=rss-39713019f427------2</link>
            <guid isPermaLink="false">https://medium.com/p/bc9ee9c9f8b7</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Samir Patil]]></dc:creator>
            <pubDate>Sun, 22 Jun 2025 15:16:43 GMT</pubDate>
            <atom:updated>2025-06-22T15:16:43.610Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5sMGe4D184qGsG5oATZ93Q.jpeg" /></figure><p>When you’re building on top of an LLM — whether it’s for code generation, image creation, or reasoning over user inputs , waiting for real-time responses can <strong>slow you down</strong>. We’ve all been there: you’ve already verified the LLM prompt and logic, but now you’re iterating on how your application processes the output. Do you really need to wait 30s to 3 minutes for every run?</p><p>Mocking saves <strong>time</strong>, speeds up <strong>feature development</strong>, and makes <strong>debugging predictable</strong>.</p><p>Here’s the better approach to mock the LLM response.</p><h4>🔍 Why mocking LLM responses is important:</h4><ol><li>You’ve already validated your prompt, no need to re-query LLMs just to test UI or downstream logic.</li><li>LLM APIs are <strong>slow</strong> (esp. for image/gen tasks), mocking makes your dev loop instant.</li><li>You want to work offline or avoid burning through API credits.</li><li>Easier <strong>unit testing</strong> of LLM-dependent flows.</li></ol><p>mock_config.json</p><pre>{<br>  &quot;analyseUserIntent&quot;: {<br>    &quot;mock&quot;: true,<br>    &quot;response&quot;: {<br>      &quot;content&quot;: &quot;User wants to create a new marketing campaign.&quot;<br>    }<br>  },<br>  &quot;generateCampaign&quot;: {<br>    &quot;mock&quot;: true,<br>    &quot;response&quot;: {<br>      &quot;content&quot;: &quot;Here&#39;s a draft campaign based on your input...&quot;<br>    }<br>  },<br>  &quot;searchSimilar&quot;: {<br>    &quot;mock&quot;: false,<br>    &quot;response&quot;: {<br>      &quot;content&quot;: &quot;Matching records found: ...&quot;<br>    }<br>  },<br>  &quot;generateImage&quot;: {<br>    &quot;mock&quot;: false,<br>    &quot;response&quot;: {<br>      &quot;content&quot;: &quot;&quot;,<br>      &quot;image_url&quot;: &quot;&quot;<br>    }<br>  }<br>}</pre><p>Here’s the Python backend function that wraps LLM usage. You can do the same in any language.</p><pre>def call_llm(purpose, user_input):<br>    if mock_config[purpose][&quot;mock&quot;]:<br>        return mock_config[purpose][&quot;response&quot;]<br>    <br>    # Actual LLM call<br>    return actual_llm_call(purpose, user_input)</pre><p>On the frontend, we use a similar approach with conditional logic based on environment or flags.</p><p>Love to hear, what are your thoughts.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bc9ee9c9f8b7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>