&lt;?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Kubernetes — Lakshmi Narasimhan</title><link>https://lakshminp.com/tags/kubernetes/</link><description>I help developers build, deploy, and distribute their SaaS without hiring a team. Long-running notes on systems, AI internals, Carnatic music, fiction craft, and whatever else collides interestingly.</description><generator>Hugo + lakshminp theme</generator><language>en-us</language><lastBuildDate>Fri, 24 Apr 2026 00:00:00 +0000</lastBuildDate><managingEditor>Lakshmi Narasimhan</managingEditor><webMaster>Lakshmi Narasimhan</webMaster><copyright>© 2026 Lakshmi Narasimhan</copyright><atom:link href="https://lakshminp.com/tags/kubernetes/feed.xml" rel="self" type="application/rss+xml"/><item><title>Your Cloud Bill Is A Tax On Someone Else's Resume</title><link>https://lakshminp.com/2026/04/cloud-bill-resume-tax/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/cloud-bill-resume-tax/</guid><category>essays</category><category>kubernetes</category><description>There’s an insurance company somewhere — real, working, profitable — with 100,000 monthly users and a peak concurrent load of about 5,000.
They spend high six figures a month on Kubernetes.
They employ twenty people to keep it running.
This story surfaced this week in the Hacker News thread on David Crawshaw’s cloud essay, and the comments section turned into a confessional. Engineer after engineer describing the same pattern: cluster adopted, cluster “optimized,” cloud spend doubled, incidents doubled, and somehow the only thing anyone can agree on is that they need to hire a platform engineer.</description><content:encoded>&lt;![CDATA[<p>There’s an insurance company somewhere — real, working, profitable — with 100,000 monthly users and a peak concurrent load of about 5,000.</p><p>They spend high six figures a month on Kubernetes.</p><p>They employ twenty people to keep it running.</p><p>This story surfaced this week in the Hacker News thread on David Crawshaw’s cloud essay, and the comments section turned into a confessional. Engineer after engineer describing the same pattern: cluster adopted, cluster “optimized,” cloud spend doubled, incidents doubled, and somehow the only thing anyone can agree on is that they need to hire a platform engineer.</p><p>You don’t. You never did. Your entire application would run on a laptop.</p><h2 id="the-incentive-nobody-likes-to-say-out-loud"><strong>The incentive nobody likes to say out loud</strong></h2><p>Here’s the quiet part: your DevOps team does not choose infrastructure based on what your application needs.</p><p>They choose it based on what their next job will pay for.</p><p>Kubernetes on a resume is worth more than Docker Compose on a resume. Terraform on a resume is worth more than “I SSH’d into the box.” Managed EKS on a resume is worth more than “I run a VM.” Every procurement decision in a modern engineering org is being made by someone who, at some level, is also writing the next page of their LinkedIn.</p><p>And management, god bless them, trusts the sales and marketing departments of Datadog and AWS and HashiCorp more than they trust their own engineers. So when someone internally says “we could do this on one server,” and someone externally sends a deck titled<em>Scaling Your Platform For The Future</em>, guess which one wins the meeting.</p><p>The decision was never technical. You just paid the technical price for it.</p><h2 id="kubernetes-is-not-the-villain-the-scale-is"><strong>Kubernetes is not the villain. The scale is.</strong></h2><p>Let’s be precise, because “Kubernetes” is doing a lot of work in this essay.</p><p>Full enterprise Kubernetes — managed control planes, service meshes, operators for everything, a dedicated platform team, Helm charts nested inside Helm charts like Russian dolls of YAML — that thing was built for Google’s problem. Multi-tenant, multi-region, thousands of services, teams that don’t talk to each other.</p><p>If your org does not look like that, you are wearing a costume.</p><p>K3s on a single VPS is not the same animal. Docker Compose on a single VPS is not the same animal. Kamal shipping containers to one Debian box is not the same animal. Those are orchestration for people who want one sane way to deploy a container, not a career in platform engineering.</p><p>The HN thread is full of<a href="https://lakshminp.com/p/kubernetes-indie-dev-alternative" class="lnp-link">engineers who moved from full K8s to one of these simpler setups</a>. The reports are boringly consistent: costs collapsed, incidents dropped, debugging became possible again. Nobody was shocked. Everyone had been waiting for permission to say it.</p><h2 id="the-solo-founders-version-of-this-trap"><strong>The solo founder’s version of this trap</strong></h2><p>You are not the insurance company. You do not have twenty people. You have you, and maybe a contractor, and a credit card that is getting nervous.</p><p>And yet — you will read the AWS Well-Architected Framework. You will follow a tutorial that starts with “first, let’s set up your VPC.” You will pay $80/month for a managed database to store 200 rows. You will provision a load balancer in front of one server. You will copy the shape of infrastructure you saw at your day job, because that shape felt legitimate, and you want to feel legitimate too.</p><p>This is how solo founders end up with a<a href="https://lakshminp.com/p/aws-is-overrated" class="lnp-link">$600/month AWS bill for an app that has six users</a>.</p><p>The shape of legitimacy is the trap. Nobody cares what your infrastructure looks like until you have customers, and once you have customers,<a href="https://lakshminp.com/p/30-dollar-saas-stack" class="lnp-link">“my app runs on one $12 VPS”</a> is a story people<em>love</em>. It’s the opposite of suspicious. It’s proof that the thing works.</p><h2 id="what-to-actually-do"><strong>What to actually do</strong></h2><ol><li><p><strong>One machine until you can’t.</strong> One VPS. One Postgres on that VPS. One reverse proxy. Docker Compose or Kamal to deploy. You are allowed to stop here for years.</p></li><li><p><strong>Scale vertically first.</strong> Hetzner will rent you a 48-core EPYC machine with 256 GB of RAM for €199/month. A mid-tier managed Kubernetes cluster on AWS starts at more than that before you’ve run a single pod. Most apps die from bad unit economics, not from running out of CPU.</p></li><li><p><strong>When you outgrow that — and you might not —<a href="https://lakshminp.com/p/when-diy-beats-managed-kubernetes" class="lnp-link">K3s on a few boxes gives you orchestration without the org chart</a>.</strong> This is the actual sweet spot for a solo operator who needs more than one machine but less than a platform team.</p></li><li><p><strong>Treat every infrastructure recommendation as a resume artifact until proven otherwise.</strong> Ask who benefits if you adopt this. If the answer is “the person telling me to adopt it,” weigh accordingly.</p></li><li><p><strong>Your cloud bill is a leading indicator of how much time you are spending on things that do not make your product better.</strong> Watch it like you watch your weight.</p></li></ol><p>The cloud was supposed to be leverage. For most people, most of the time, it has become the opposite: a recurring invoice for someone else’s credibility.</p><p>You are allowed to just run the server.</p>
]]></content:encoded></item><item><title>My Agent Runs 10 Cron Jobs. Three of Them Are Worth the Electricity.</title><link>https://lakshminp.com/2026/04/ai-agent-cron-jobs-worth-it/</link><pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/ai-agent-cron-jobs-worth-it/</guid><category>essays</category><category>kubernetes</category><category>ai-coding</category><description>I have a daemon that runs on a server. It’s been up for seven weeks. It has ten scheduled jobs — some hourly, some daily, some weekly. Or at least, that’s what’s on paper.
This is what people are calling “the future of work.”
I’m not sure it is. I’m sure it’s what sells on Twitter.
The demo economy Always-on agents photograph well. That’s most of what’s going on.
“My agent posted while I slept” is tweetable in a way that “I wrote a cron job” isn’t, even when the outputs are identical. The demo-industrial complex has figured this out. YouTubers build daemons. Framework authors build daemons. There are now three different subreddits comparing daemons. The flywheel is real, the content is prolific, and very little of it is honest about what the daemon is actually producing.</description><content:encoded>&lt;![CDATA[<p>I have a daemon that runs on a server. It’s been up for seven weeks. It has ten scheduled jobs — some hourly, some daily, some weekly. Or at least, that’s what’s on paper.</p><p>This is what people are calling “the future of work.”</p><p>I’m not sure it is. I’m sure it’s what sells on Twitter.</p><h1 id="the-demo-economy"><strong>The demo economy</strong></h1><p>Always-on agents photograph well. That’s most of what’s going on.</p><p>“My agent posted while I slept” is tweetable in a way that “I wrote a cron job” isn’t, even when the outputs are identical. The demo-industrial complex has figured this out. YouTubers build daemons. Framework authors build daemons. There are now three different subreddits comparing daemons. The flywheel is real, the content is prolific, and very little of it is honest about what the daemon is actually producing.</p><p>The hype bundles together several different things that deserve to be separated:</p><ol><li><p>Agents that<em>run work while you’re asleep</em> (useful, conditionally)</p></li><li><p>Agents that<em>react to things happening in the world</em> (useful, conditionally)</p></li><li><p>Agents that<em>capture things as they happen on your phone</em> (useful, conditionally)</p></li><li><p>Agents that<em>run heartbeats and ask themselves what to do</em> (pure performance art)</p></li><li><p>Agents that<em>self-evolve in a loop in the background</em> (fun demos, almost no output)</p></li><li><p>Agents that<em>spawn a hundred parallel subagents to research a topic</em> (almost always worse than one good search)</p></li></ol><p>The hype treats all six as the same thing. They aren’t.</p><h1 id="the-20-that-actually-earns-its-keep"><strong>The 20% that actually earns its keep</strong></h1><p>Honest list of when a background daemon does something a CLI or a 10-line bash cron can’t:</p><p><strong>Scheduled work that has to happen when you’re not there.</strong> Crawl competitor sites at 3am. Pull last night’s Sentry errors. Summarize overnight industry chatter into a 7am brief. Your laptop is off, something has to be running somewhere. Legitimate.</p><p><strong>Reactive triggers on external events.</strong></p><p>Email arrives -&gt; triage.</p><p>Substack comment -&gt; draft reply.</p><p>Sentry alert -&gt; diagnose + suggest fix.</p><p>The trigger comes from outside; compute has to meet it. Legitimate if the volume actually warrants automation (if you get three emails a day, triage is a solved problem — your inbox).</p><p><strong>On-the-move capture.</strong></p><p>Voice memo from your phone -&gt; transcribed -&gt; landed in memory.</p><p>Forwarding a link from your phone to your agent. The value is that capture happens when inspired, not when at desk. Real lift for content creators who have thoughts in elevators.</p><p><strong>Judgment-laden monitoring.</strong></p><p>Not “disk at 80%” — any shell script can do that.<em>“Disk at 80% AND growing 2% per hour AND that’s unusual for this host.”</em></p><p>Requires context; needs to know what normal looks like. This is where LLMs in a daemon genuinely beat a threshold-based alerting stack.</p><p>That’s it. Four categories. Anything else is mostly burning tokens.</p><h1 id="the-80-thats-noise"><strong>The 80% that’s noise</strong></h1><p><strong>Heartbeats that ask the agent “anything to do?”</strong></p><p>The agent wakes up, loads context, decides there isn’t anything to do, goes back to sleep. You pay for the loaded context every time. Over a day this adds up to real money for the privilege of watching an agent shrug.</p><p><strong>Self-evolution loops.</strong></p><p>“The agent improves itself while you sleep.” What it’s usually doing is refactoring its own prompts in circles. Cool demo on YouTube. Zero measurable outcome delta after a month of running.</p><p><strong>Parallel subagent fan-out for research.</strong></p><p>Ten agents search the web about the same question and return ten lightly-paraphrased versions of the same top three results. One focused 10-minute session beats this, almost always.</p><p><strong>“Long-running overnight research tasks.”</strong></p><p>When the output lands in your morning inbox, is it better than what 30 focused minutes at your desk would produce? Honestly check. Usually no.</p><p><strong>Replacing things you could cron in 10 lines of bash.</strong></p><p>The test: could a $5 VPS with a shell script + cron +<code>jq</code> do this? If yes, you’re not using AI for the part that needs AI. You’re using it because daemons are cool.</p><h1 id="receipts-whats-actually-on-my-vm"><strong>Receipts: what’s actually on my VM</strong></h1><p>I pulled the daemon’s state file and the log directory while writing this. Fifty-four days of uptime. Ten jobs on paper. The picture is worse than I thought.</p><p>Three are running reliably.</p><p><code>sentry-monitor</code> has fired 191 times since early March. Latest run: this morning. When the night throws errors it reads them, groups them, and suggests a fix — not a link to the stack trace, an actual “here’s what’s probably wrong and here’s the one-line change.” Category 2 plus category 4. Keep.</p><p><code>infra-health</code> has fired 190 times on basically the same cadence. Knows what normal looks like per host. Stays quiet when a disk spike is a scheduled backup and shouts when it isn’t. Category 4. The whole reason an LLM beats a thresholds-and-Prometheus stack here, and no, you cannot Grafana your way to this in under six months of tuning. Keep.</p><p><code>scout</code> has fired 71 times across seven weeks. Daily-ish. Scans Reddit, HN, and Substack for signal that feeds this blog’s content calendar. I<em>do</em> use the output. Category 2 if I’m generous. Keep — but it absorbs the next two jobs on the list below.</p><p>Now the uncomfortable part.</p><p>Three of the ten have straight-up stopped running and I didn’t notice.</p><p><code>morning-brief</code> was scheduled daily at 6am. It last fired on March 18. A full month of no overnight brief. I did not miss it. I did not investigate. I did not know.</p><p><code>seo-audit</code> was weekly. It has run exactly once in the daemon’s entire fifty-four-day lifetime, on March 1. Seven missed weeks. Nobody wrote a bug report to themselves. Nobody opened a file that wasn’t there.</p><p><code>auto-draft</code> was supposed to produce a draft post every day. It has run exactly once, on April 11. Eight days of silence. Also unnoticed.</p><p>If a job stopped running a month ago and you didn’t miss it, the job was never producing anything that mattered. That’s not my heuristic. That’s the audit, evaluating itself while I was busy talking about audits on Twitter.</p><p>Four more are in some stage of limping.</p><p><code>reddit-scan</code> — 27 runs over 45 days, last one April 10. Running, sort of, when the mood takes it. Nine days of silence so far on that one.</p><p><code>x-scan</code> — identical pattern to reddit-scan. Same overlap. Same drift. Same silence since April 10. These two were supposed to be complementary; they’ve turned out to be redundant<em>and</em> unreliable, which is a rare trick.</p><p><code>engagement-brief</code> — four runs, total, in the job’s entire lifetime. Not daily. Not weekly. More like “occasionally, if the stars align.”</p><p><code>x-analytics</code> — three runs, last one March 16. Effectively dead, which is fine, because I check my X numbers roughly once a month anyway.</p><p>Final tally, the honest one.</p><p>Three jobs firing on schedule, producing output I use. Three jobs that silently stopped weeks ago and nobody in this house noticed, including me. Four jobs wandering between “running” and “not really” with no clear reason why.</p><p>Three-of-ten is the optimistic read. The pessimistic read is that six of the ten audited themselves — they cut themselves by going quiet, and I hadn’t even done them the courtesy of looking.</p><p>This is from someone who builds daemons for a living and writes about them for a job. What do you think yours looks like under the hood?</p><h1 id="the-five-question-self-test"><strong>The five-question self-test</strong></h1><p>Before you keep any always-on agent job, make it answer these:</p><ol><li><p><strong>Would I actually miss this if it stopped?</strong> If you turned it off for two weeks and no one noticed, it’s not producing value. It’s producing comfort.</p></li><li><p><strong>Does the cadence match downstream consumption?</strong> A job that fires 4x/day for output you read weekly is 27 extra runs a week of pure overhead.</p></li><li><p><strong>Is the trigger genuinely external?</strong> (Scheduled time, incoming event, captured input.) If the agent is just checking on itself, you’ve built a Roomba that vacuums an empty room.</p></li><li><p><strong>Could a shell script + cron +</strong><code>jq</code><strong>do this?</strong> If yes, you’re not using AI for the part that needs AI.</p></li><li><p><strong>Does the output change my behaviour?</strong> If yesterday’s run and last Thursday’s run would have produced the same action from me (or none), one of them was wasted.</p></li></ol><p>Honest answers will cull your cron list by half. Mine certainly did, once I stopped writing this post and actually did the audit.</p><h1 id="what-this-isnt-saying"><strong>What this isn’t saying</strong></h1><p>I’m not arguing against always-on agents. I’m arguing against always-on agents that<em>aren’t doing anything.</em></p><p>There’s real value when the conditions line up — work-while-you-sleep, external-trigger-response, on-the-move-capture, judgment-laden-monitoring. The reason I keep the daemon running (even after cutting half its jobs) is those four categories genuinely earn the monthly subscription. The reason I’m writing this is that the other six patterns — the ones that photograph well — are funding a lot of framework development and not much measurable outcome.</p><p>If your agent is doing category 1-4 work, the hype is warranted. If it’s doing category 5-6 work, you’re paying a subscription to a demo.</p><p>The uncomfortable question for most of the agent-community content right now is<em>which category is the thing being demoed, really?</em> And whether the person demoing it has done the five-question audit on their own cron list.</p><p>My guess: very few have. The demo economy doesn’t reward the audit. It rewards the screenshot of the agent waking up at 3am and pretending to be useful.</p>
]]></content:encoded></item><item><title>The $30/Year Stack for Launching Small Bets</title><link>https://lakshminp.com/2026/01/30-dollar-saas-stack/</link><pubDate>Mon, 19 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/30-dollar-saas-stack/</guid><category>essays</category><category>saas</category><category>kubernetes</category><description>Every time I launch a new small bet, I need the same boring stuff: professional email, a chat widget, uptime monitoring. The kind of infrastructure that’s completely unsexy but makes you look like you have your act together.
For years, I overcomplicated this. Custom SMTP servers. Self-hosted monitoring. Elaborate setups that took days to configure and broke whenever I looked at them wrong.
Then I realized something: I was spending more time on infrastructure than on validating whether anyone wanted my product.</description><content:encoded>&lt;![CDATA[<p>Every time I launch a new small bet, I need the same boring stuff: professional email, a chat widget, uptime monitoring. The kind of infrastructure that’s completely unsexy but makes you look like you have your act together.</p><p>For years, I overcomplicated this. Custom SMTP servers. Self-hosted monitoring. Elaborate setups that took days to configure and broke whenever I looked at them wrong.</p><p>Then I realized something: I was spending more time on infrastructure than on validating whether anyone wanted my product.</p><p>So I built a repeatable stack. Total cost: about $30-42 per year, per small bet. Here’s the whole thing.</p><h2 id="domain--hosting-cloudflare-free"><strong>Domain &amp; Hosting: Cloudflare (Free)</strong></h2><p>Buy your domain wherever you want, but point the nameservers to Cloudflare immediately.</p><p>Cloudflare’s free tier is absurd:</p><ul><li><p>DNS management (fast, reliable)</p></li><li><p>Free SSL certificates (automatic)</p></li><li><p>DDoS protection</p></li><li><p>CDN caching</p></li><li><p>Cloudflare Pages (unlimited sites, unlimited bandwidth)</p></li></ul><p>That last one is key. Your landing page goes on Cloudflare Pages. Connect your repo, push to main, it deploys. No servers. No bills. No thinking about infrastructure when you should be thinking about whether anyone wants your product.</p><p>I run every small bet’s landing page on CF Pages. Zero hosting cost.</p><h2 id="email-google-workspace-the-india-pricing-hack"><strong>Email: Google Workspace (The India Pricing Hack)</strong></h2><p>You want professional email.<code>hello@yourdomain.com</code>, not<code>yourdomain.help@gmail.com</code> like some kind of digital nomad running a dropshipping scam.</p><p>Google Workspace direct pricing: $6/month. Painful when you’re running multiple bets.</p><p>Google Workspace through an Indian reseller: Rs.125/month. That’s roughly $1.50.</p><p>Same product. Same Gmail experience. Same everything. Just&hellip; cheaper, because regional pricing exists and Google apparently forgot to close this loophole.</p><p>Recommended resellers: Medha Cloud, Host IT Smart, Shivaami. They’re authorized, they’re legit, and they’ll save you $50+/year per domain.</p><p>Setup takes 30 minutes: verify domain, add MX records, configure SPF/DKIM/DMARC so your emails don’t land in spam. Done.</p><h2 id="support-crisp-chat-free"><strong>Support: Crisp Chat (Free)</strong></h2><p>Intercom wants $74/month. For a small bet that might make $0.</p><p>Crisp’s free tier gives you:</p><ul><li><p>2 team seats (it’s just you anyway)</p></li><li><p>Unlimited conversations</p></li><li><p>Mobile app for notifications</p></li><li><p>A widget that doesn’t look like it was designed in 2008</p></li></ul><p>Copy-paste their script tag into your landing page. Five minutes.</p><p>Upgrade trigger: when you have so many support conversations that you need automation. Which means you have customers. Which means you can afford to pay for things.</p><h2 id="monitoring-betterstack-free"><strong>Monitoring: BetterStack (Free)</strong></h2><p>Your app will go down at 3am on a Sunday. This is not a prediction, it’s a guarantee.</p><p>BetterStack’s free tier:</p><ul><li><p>10 uptime monitors</p></li><li><p>1GB logs/month</p></li><li><p>Email and Slack alerts</p></li><li><p>3-day log retention</p></li></ul><p>Is 3-day retention enough? For a small bet you’re validating? Yes. You’re not running a bank.</p><p>Alternative: Axiom gives you 500GB ingest and 30-day retention if you’re logging more aggressively. Also free.</p><h2 id="error-tracking-sentry-free"><strong>Error Tracking: Sentry (Free)</strong></h2><p>Your code will throw exceptions in production that never happened locally. Classic.</p><p>Sentry’s free tier:</p><ul><li><p>5K errors/month</p></li><li><p>10K performance transactions</p></li><li><p>1 user</p></li><li><p>90-day retention</p></li></ul><p>For a small bet, 5K errors/month is plenty. If you’re hitting that limit, either your app is broken or you have enough users to pay for it.</p><h2 id="database-supabase-free-tier-or-self-hosted"><strong>Database: Supabase (Free Tier or Self-Hosted)</strong></h2><p>Every small bet needs a database. Supabase’s free tier is genuinely useful:</p><ul><li><p>500MB database</p></li><li><p>1GB file storage</p></li><li><p>50K monthly active users</p></li><li><p>Unlimited API requests</p></li></ul><p>That’s enough to validate most ideas. The catch: you get 2 free projects total. After that, it’s $25/month per project.</p><p>For small bets that graduate to real products, I self-host Supabase on a $6/month Hetzner VPS. Full Postgres, auth, storage, realtime — no project limits, no usage caps. (I’m building a service called<a href="https://supabyoi.com/" rel="external nofollow noopener" class="lnp-link">Supabyoi</a> to make this dead simple. More on that soon.)</p><h2 id="the-complete-stack"><strong>The Complete Stack</strong></h2><ul><li><p><strong>Domain</strong> — ~$10-15/year</p></li><li><p><strong>Cloudflare (DNS + Pages)</strong> — Free</p></li><li><p><strong>Google Workspace (India)</strong> — ~1.50/month( 1.50/<em>month</em>( 18/year)</p></li><li><p><strong>Crisp</strong> — Free</p></li><li><p><strong>BetterStack</strong> — Free</p></li><li><p><strong>Sentry</strong> — Free</p></li><li><p><strong>Supabase</strong> — Free</p></li></ul><p><strong>Total: ~1.50/month, 1.50/<em><strong><strong>month</strong></strong></em>, 30-42/year</strong></p><p>That’s DNS, hosting, professional email, live chat, uptime monitoring, error tracking, and a database for less than a single month of most “startup” tools.</p><h2 id="the-rules"><strong>The Rules</strong></h2><p><strong>Don’t upgrade until you have paying customers.</strong> Free tiers exist for validation. Use them.</p><p><strong>Keep the setup identical across bets.</strong> Same tools, same patterns, same DNS records. You should be able to launch a new bet’s infrastructure in an afternoon, not a weekend.</p><p><strong>Resist the urge to self-host.</strong> Yes, you<em>can</em> run your own mail server. You can also perform your own dental surgery. Neither is advisable.</p><h2 id="when-to-actually-upgrade"><strong>When To Actually Upgrade</strong></h2><ul><li><p><strong>Google Workspace</strong> — You need &gt;30GB storage → $7/mo</p></li><li><p><strong>Crisp</strong> — You need chatbots or &gt;2 team members → $25/mo</p></li><li><p><strong>BetterStack</strong> — You’re pushing &gt;1GB logs/month → $24/mo</p></li><li><p><strong>Sentry</strong> — You’re hitting 5K errors/month → $26/mo</p></li><li><p><strong>Supabase</strong> — You need &gt;2 projects or more storage → $25/mo (or self-host)</p></li></ul><p>Notice a pattern? These are all “you have real traction” problems. Good problems to have.</p><h2 id="whats-not-covered-yet"><strong>What’s Not Covered (Yet)</strong></h2><p>This is the skeleton — the basic infrastructure every small bet needs from day one.</p><p>I’ll cover these in separate posts:</p><ul><li><p><strong>Tech stack choices</strong> (frameworks, languages, deployment)</p></li><li><p><strong>Payment processing</strong> (Stripe, Lemon Squeezy, regional considerations)</p></li><li><p><strong>CI/CD pipelines</strong> (GitHub Actions, deployment automation)</p></li><li><p><strong>Landing page patterns</strong> (what actually converts)</p></li></ul><p>One thing at a time.</p><h2 id="the-point"><strong>The Point</strong></h2><p>Infrastructure should be invisible. It should cost almost nothing while you’re validating. It should scale up only when you have revenue to pay for it.</p><p>$30/year per bet means you can run 10 small bets for less than most people pay for a single Notion subscription.</p><p>Stop building infrastructure. Start shipping products.</p><p><em>This is part of my “Deploy” series — simple infrastructure patterns for solo operators who’d rather build products than manage servers.</em></p>
]]></content:encoded></item><item><title>I Found a Cryptominer in My Client's Production Cluster. Claude Code Found the Attacker.</title><link>https://lakshminp.com/2026/01/cryptominer-production-cluster/</link><pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/cryptominer-production-cluster/</guid><category>essays</category><category>kubernetes</category><category>claude-code</category><description>New Year’s Day. Coffee in hand. Ready to ease back into work.
Then I saw the logs.
2026-01-02T06:34:27 GET xmrig-6.24.0-linux-static-x64.tar.gz 2026-01-02T06:34:30 GET http://37.32.6.33:7979/m 2026-01-02T06:34:30 spawn /opt/systemf/m ENOENT xmrig. In production. Someone was mining Monero on my client’s Kubernetes cluster.
The horror.
The Investigation I had a few hundred megabytes of JSON logs and approximately zero patience for manually correlating timestamps. So I did what any reasonable person would do: I asked Claude Code to analyze the logs and figure out what triggered the miner download.</description><content:encoded>&lt;![CDATA[<p>New Year’s Day. Coffee in hand. Ready to ease back into work.</p><p>Then I saw the logs.</p><pre><code>2026-01-02T06:34:27 GET xmrig-6.24.0-linux-static-x64.tar.gz
2026-01-02T06:34:30 GET http://37.32.6.33:7979/m
2026-01-02T06:34:30 spawn /opt/systemf/m ENOENT</code></pre><p>xmrig. In production. Someone was mining Monero on my client’s Kubernetes cluster.</p><p>The horror.</p><h2 id="the-investigation"><strong>The Investigation</strong></h2><p>I had a few hundred megabytes of JSON logs and approximately zero patience for manually correlating timestamps. So I did what any reasonable person would do: I asked Claude Code to analyze the logs and figure out what triggered the miner download.</p><p>Within seconds, it built a timeline:</p><p><strong>Time Event</strong></p><p>06:34:26. Normal request to /onboarding</p><p>06:34:27. xmrig downloaded from GitHub</p><p>06:34:30. Secondary payload from sketchy IP</p><p>06:34:57. Container OOMKilled</p><p>The cryptominer was so resource-hungry it consumed 2GB of memory in 30 seconds and crashed the container. Ironic. The attacker’s greed saved us from a prolonged compromise.</p><p>But how did they get in?</p><h2 id="chasing-red-herrings"><strong>Chasing Red Herrings</strong></h2><p>Claude Code’s first suspect: a low-version npm package called<code>device-unique-keygen</code>. Added by a developer whose email matched the package maintainer. Classic supply chain attack pattern.</p><p>I got excited. Maybe too excited.</p><p>Claude Code fetched the GitHub repo, analyzed the source code, checked for postinstall scripts, looked for obfuscated code, searched for eval() calls.</p><p>Nothing. The package was clean. Just a browser fingerprinting library. Boring. Legitimate.</p><p>We moved on.</p><p>No malicious init containers. No sidecars. No .ashrc shenanigans. The Dockerfile was clean. The pod spec was clean.</p><p>Everything was clean except someone was definitely mining crypto on our infrastructure.</p><h2 id="the-actual-answer"><strong>The Actual Answer</strong></h2><p>Claude Code ran<code>npm audit</code> on the codebase.</p><pre><code>critical │ Next.js is vulnerable to RCE in React flight protocol
Package │ next
Patched │ &gt;=15.3.6
Your ver │ 15.3.4
CVSS │ 10.0</code></pre><figure><a href="https://substackcdn.com/image/fetch/$s_!FrM8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":765,"width":1247,"resizeWidth":null,"bytes":128814,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/183314492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!FrM8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 424w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 848w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 1272w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 1456w" sizes="100vw" loading="lazy" width="1247" height="765"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p>CVSS 10. The maximum possible score. The “your house is actively on fire” of security ratings.</p><p>The app was running Next.js 15.3.4. A publicly disclosed RCE vulnerability. No authentication required. An attacker could run arbitrary commands on the server by sending a crafted request.</p><p>That’s exactly what happened. They sent a request, ran wget twice, downloaded the miner, and started extracting crypto value from compute cycles they weren’t paying for.</p><p>The container’s memory limit stopped them. A $20/month Kubernetes resource limit prevented what could have been ongoing theft.</p><h2 id="what-claude-code-actually-did"><strong>What Claude Code Actually Did</strong></h2><p>I want to be clear about what happened here. I didn’t single-handedly unravel a sophisticated attack. I didn’t manually correlate log timestamps or reverse-engineer obfuscated npm packages.</p><p>I said “check these logs” and Claude Code:</p><ul><li><p>Built a timeline from JSON log entries</p></li><li><p>Identified the malware artifacts and C2 server</p></li><li><p>Traced git blame to find who added suspicious packages</p></li><li><p>Fetched and analyzed source code from GitHub</p></li><li><p>Ruled out attack vectors one by one</p></li><li><p>Found the actual vulnerability via npm audit</p></li><li><p>Correlated the OOMKill timing with the attack</p></li><li><p>Suggested remediation and forensic preservation steps</p></li></ul><p>The entire investigation took under an hour. Not because I’m fast. Because Claude Code is.</p><h2 id="the-fix"><strong>The Fix</strong></h2><pre><code>pnpm update next@^15.3.6</code></pre><p>One command. That’s the remediation for a CVSS 10.0 vulnerability.</p><p>We also orphaned the compromised pods for forensic analysis, rotated secrets, and added proper security contexts to prevent future wget adventures.</p><figure><a href="https://substackcdn.com/image/fetch/$s_!bCvw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":772,"width":1344,"resizeWidth":null,"bytes":121268,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/183314492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!bCvw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 424w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 848w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 1272w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 1456w" sizes="100vw" loading="lazy" width="1344" height="772"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><h2 id="the-lesson"><strong>The Lesson</strong></h2><p>Two things saved us:</p><ol><li><p>Centralized logging (couldn’t investigate without the logs)</p></li><li><p>Memory limits (the attacker’s miner killed itself)</p></li></ol><p>One thing would have prevented this entirely: running<code>npm audit</code> before deployment.</p><p>The attacker exploited a vulnerability that was publicly disclosed and patched. We just hadn’t updated yet.</p><p>Godspeed with your own dependency updates.</p><p>My Medium friends can read this<a href="https://medium.com/@lakshminp/i-found-a-cryptominer-in-my-clients-production-cluster-claude-code-found-the-attacker-ae6148ec0514" rel="external nofollow noopener" class="lnp-link">over there</a> as well.</p>
]]></content:encoded></item><item><title>I spent years on Kubernetes. Now I'm betting against it.</title><link>https://lakshminp.com/2025/12/kubernetes-indie-dev-alternative/</link><pubDate>Thu, 04 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/kubernetes-indie-dev-alternative/</guid><category>essays</category><category>kubernetes</category><description>I’ve spent years in the Kubernetes ecosystem. I wrote about K3s. I ran production clusters. I know my way around kubectl, Helm charts, and the CNCF landscape.
And I’m building a deployment tool that doesn’t use any of it.
Here’s why.
Kubernetes solves problems you don’t have K8s is incredible engineering. It solves real problems:
Multi-team deployments without stepping on each other
Automatic failover across dozens of nodes
Fine-grained resource allocation at massive scale</description><content:encoded>&lt;![CDATA[<p>I’ve spent years in the Kubernetes ecosystem. I wrote about K3s. I ran production clusters. I know my way around kubectl, Helm charts, and the CNCF landscape.</p><p>And I’m building a deployment tool that doesn’t use any of it.</p><p>Here’s why.</p><h2 id="kubernetes-solves-problems-you-dont-have"><strong>Kubernetes solves problems you don’t have</strong></h2><p>K8s is incredible engineering. It solves real problems:</p><ul><li><p>Multi-team deployments without stepping on each other</p></li><li><p>Automatic failover across dozens of nodes</p></li><li><p>Fine-grained resource allocation at massive scale</p></li><li><p>Rolling updates for services with thousands of instances</p></li></ul><p>If you’re Spotify, you need this. If you’re running a 50-person engineering org, you need this.</p><p>If you’re a solo dev with one FastAPI app and a Celery worker? You don’t.</p><p>As one dev put it: “Do you want to build a product, or do you want to build an infrastructure team? Kubernetes makes sense for the latter, but it’s often overkill for the former.”</p><p>You need:</p><ul><li><p>git push → app is live</p></li><li><p>Rollback when you break something</p></li><li><p>Logs you can actually read</p></li><li><p>Alerts when the site goes down</p></li></ul><p>That’s it. Everything else is ceremony.</p><h2 id="the-hidden-cost-isnt-the-cluster"><strong>The hidden cost isn’t the cluster</strong></h2><p>“But K3s is lightweight! You can run it on a $6 VPS!”</p><p>True. I’ve done it. Here’s what they don’t tell you:</p><p>A solo dev<a href="https://www.reddit.com/r/kubernetes/comments/1p2k9xd/solo_dev_tired_of_k8s_churn_what_are_my_options/" rel="external nofollow noopener" class="lnp-link">recently posted</a> on r/kubernetes with a title that said it all: “Solo dev tired of K8s churn&hellip; What are my options?”</p><p>His pain point wasn’t learning Kubernetes. It was the maintenance:</p><blockquote><p>“I don’t mind learning the topics and writing the config, I do mind having to deal with a lot of work out of nowhere just because the underlying tools are beyond my control and requiring breaking updates.”</p></blockquote><p>He’d been burned by Bitnami charts pulling the rug, NGINX ingress breaking changes. Things that worked stopped working — not because he changed anything, but because the ecosystem did.</p><blockquote><p>“It all felt very straightforward, and it worked so well for a bit, but it starts to crumble even when I haven’t changed anything on my side.”</p></blockquote><p>This is the hidden cost. Not the setup — the churn.</p><p><strong>The YAML tax</strong>: Every change requires editing manifests. Add an env var? YAML. Change a port? YAML. Want a cron job? That’s a whole new CronJob resource. One team had a production outage caused by an improperly indented YAML line. A single space broke prod.</p><p><strong>The debugging tax</strong>: Something’s wrong. Is it the pod? The service? The ingress? The network policy? The PVC? Hope you remember how to read<code>kubectl describe</code>.</p><p><strong>The upgrade tax</strong>: K3s made this easier, but you’re still running a distributed system. A 2024 report found over 77% of Kubernetes practitioners still have issues running their clusters — up from 66% in 2022. It’s getting harder, not easier.</p><p><strong>The cognitive tax</strong>: Part of your brain is always allocated to “how does Kubernetes work” instead of “how do I ship features.”</p><p>As one commenter put it: “Choose your churn.” There’s always something.</p><p>The Reddit OP’s conclusion? He gave up on K8s entirely. Settled on plain NixOS on a single Hetzner VPS. Accepted that 99.9% uptime from one server is good enough. Skipped the redundancy he thought he needed.</p><blockquote><p>“I am trying to write my software, I just want a reliable thing to host it with the freedom and reliability that one would expect from a system that stays out of your way.”</p></blockquote><p>That’s the real ask. A system that stays out of your way.</p><p>For teams, the Kubernetes tax is worth paying. You split it across people, you build expertise, you amortize the cost.</p><p>Solo? You pay it all yourself, every time.</p><h2 id="what-actually-works-for-solo-devs"><strong>What actually works for solo devs</strong></h2><p>So if not Kubernetes, what?</p><p>The same Reddit OP nailed the PaaS problem too:</p><blockquote><p>“These ‘managed-docker’ services charge per container/pod and force the user to over-provision. Your pod doesn’t run on 250mb RAM? Ok pay for 1GB even though you only need 500mb.”</p></blockquote><p>I’ve tried everything:</p><ul><li><p>Heroku (great until the bill hits)</p></li><li><p>Railway/Render (same story, nicer UX — $50-100/mo for what costs $5 on a VPS)</p></li><li><p>Dokku (solid, but showing its age)</p></li><li><p>Coolify (powerful, but now you’re babysitting another server)</p></li><li><p>K3s (overkill for most solo projects)</p></li><li><p>Raw Docker + nginx (works but tedious)</p></li></ul><p>The best setup I’ve found:<strong>Kamal</strong>.</p><p>It’s from 37signals. They run Basecamp and HEY on it. It’s just Docker + SSH. No cluster, no orchestrator, no YAML manifests.</p><pre><code>kamal deploy</code></pre><p>That’s it. It SSHs into your server, pulls your container, does a zero-downtime swap. Rollback is one command. Logs are one command.</p><p>It’s boring. It works.</p><h2 id="my-bet-ai-interface--dashboards--cli--yaml"><strong>My bet: AI interface &gt; dashboards &gt; CLI &gt; YAML</strong></h2><p>Here’s where it gets interesting.</p><p>Kamal solved the “deploy” problem. But ops is more than deploy:</p><ul><li><p>Why is the app slow right now?</p></li><li><p>What happened at 3am?</p></li><li><p>Should I upgrade my VM or optimize my code?</p></li><li><p>Show me the errors from the last hour</p></li></ul><p>These questions require jumping between tools. SSH into the box, grep the logs, check Grafana, cross-reference with your deploy history.</p><p>My bet: you shouldn’t need to do any of that.</p><p>You should just ask.</p><p>“Why is memory usage spiking?” → Here’s what’s using RAM, and here’s the trend over the last week.</p><p>“Roll back to yesterday’s deploy” → Done. Here’s what changed.</p><p>“Show me errors from the /api/checkout endpoint” → Found 47 errors, here’s the pattern.</p><p>This isn’t science fiction. LLMs are good at this now. The interface just doesn’t exist yet.</p><h2 id="what-im-building"><strong>What I’m building</strong></h2><p>VMKit is my attempt at this interface.</p><ul><li><p>Bring your own VPS (Hetzner, DigitalOcean, whatever)</p></li><li><p>It handles Kamal, Traefik, SSL, monitoring</p></li><li><p>The interface is conversation — web chat or MCP server in Claude Code</p></li></ul><p>No Kubernetes. No YAML manifests. No 47-screen dashboards.</p><p>Just say what you want.</p><p>I might be wrong. Maybe solo devs actually love clicking through Render’s UI. Maybe the Kubernetes complexity is worth it for everyone.</p><p>But I don’t think so. I think the right answer for one person running one to three apps is radically simpler than what we have today.</p><p><a href="https://vmkit.dev/" rel="external nofollow noopener" class="lnp-link">vmkit.dev</a> if you want to follow along.</p><h2 id="the-uncomfortable-truth"><strong>The uncomfortable truth</strong></h2><p>I’m not anti-Kubernetes. I’m anti-complexity-for-its-own-sake.</p><p>K8s is a tool. An incredibly powerful one. But tools have contexts where they make sense and contexts where they don’t.</p><p>Solo dev shipping a SaaS? You don’t need pod autoscaling. You need deploys that work and a way to debug when they don’t.</p><p>That’s the bet.</p>
]]></content:encoded></item><item><title>AWS Is Overrated</title><link>https://lakshminp.com/2025/10/aws-is-overrated/</link><pubDate>Sat, 11 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/aws-is-overrated/</guid><category>essays</category><category>kubernetes</category><description>If you’re an indie dev building your first SaaS, AWS is not your friend.
It’s a maze of services, dashboards, and acronyms pretending to make you productive while quietly billing you for curiosity.
Sure, it’s “the industry standard.” But here’s the thing: you’re not Netflix. You’re not Stripe. You don’t need fifteen managed services to ship an MVP. You just need one working prototype in front of users.
When I started shipping my own SaaS projects, I defaulted to AWS too. Everyone said it was the “serious” choice. I spun up EC2s, tinkered with VPCs, IAM roles, and CloudWatch dashboards.</description><content:encoded>&lt;![CDATA[<p>If you’re an indie dev building your first SaaS, AWS is not your friend.</p><p>It’s a maze of services, dashboards, and acronyms pretending to make you productive while quietly billing you for curiosity.</p><p>Sure, it’s “the industry standard.” But here’s the thing: you’re not Netflix. You’re not Stripe. You don’t need fifteen managed services to ship an MVP. You just need one working prototype in front of users.</p><p>When I started shipping my own SaaS projects, I defaulted to AWS too. Everyone said it was the “serious” choice. I spun up EC2s, tinkered with VPCs, IAM roles, and CloudWatch dashboards.</p><p>Two weeks later, my app still wasn’t live. But my bill was.</p><p>That’s when it clicked. AWS is optimized for<em>scale,</em> not<em>speed.</em> It’s designed for teams with DevOps pipelines, budgets, and compliance officers. Indie devs have none of those.</p><p><strong>Here’s the real problem:</strong></p><blockquote><p>AWS makes you<em>feel</em> productive because it has a service for everything.</p></blockquote><p>But it slows you down because you end up<em>assembling</em> infrastructure instead of shipping software.</p><p>You’re busy wiring VPCs while your users are waiting for a login page.</p><p>If you’re building your first SaaS, you’re better off with:</p><ul><li><p><strong>Render</strong> or<strong>Fly.io</strong> for fast deploys.</p></li><li><p><strong>Railway</strong>,<strong>Supabase</strong> if you love simplicity.</p></li><li><p>DigitalOcean app platform</p></li><li><p>Or even your own<strong>K3s</strong> box on a $30 DigitalOcean droplet if you like to tinker.(More on this in future posts)</p></li></ul><p>You’ll have full control, predictable costs, and a deploy story you can explain in a single sentence.</p><p>That’s what matters at your stage — not five-nines availability across three regions.</p><p>AWS will always have its place. It’s incredible at running serious workloads, regulated systems, and multi-tenant platforms at scale.</p><p>But for indie devs trying to launch, learn, and iterate fast — it’s<em>overkill.</em></p><p>Use the simplest stack that lets you ship.</p><p>Add complexity only when success forces you to.</p><p>Because nothing kills momentum faster than debugging IAM policies instead of building features.</p><h2 id="tldr">TL;DR</h2><p>If you’re a solo founder or small team, your advantage isn’t scale — it’s speed.</p><p>Don’t trade that away for a cloud that was never built for you.</p><p>I share one short post daily-ish for productive indie developers — how to ship faster, cheaper, and saner. Subscribe if that’s your vibe.</p>
]]></content:encoded></item><item><title>Why Your SaaS Needs a Docker Compose Setup Even If You’re Just One Person</title><link>https://lakshminp.com/2025/10/saas-docker-compose-setup/</link><pubDate>Fri, 10 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/saas-docker-compose-setup/</guid><category>essays</category><category>kubernetes</category><category>saas</category><description>If you’re building a SaaS solo, the biggest productivity killer isn’t writing code — it’s setting up your damn environment.
You know the story:
You clone your repo on a new laptop or spin up a new dev box, run flask run or uvicorn main:app &amp;ndash;reload, and boom — connection refused on localhost:5432.
Postgres isn’t running.
Your .env file is half missing.
Supabase changed a port.
And now you’re googling “how to reset a Postgres user password” for the third time this month.</description><content:encoded>&lt;![CDATA[<p>If you’re building a SaaS solo, the biggest productivity killer isn’t writing code — it’s<em>setting up your damn environment</em>.</p><p>You know the story:</p><p>You clone your repo on a new laptop or spin up a new dev box, run flask run or uvicorn main:app &ndash;reload, and boom — connection refused on localhost:5432.</p><p>Postgres isn’t running.</p><p>Your .env file is half missing.</p><p>Supabase changed a port.</p><p>And now you’re googling “how to reset a Postgres user password” for the third time this month.</p><p>That’s why I’ve stopped messing around with manual setups — and started containerizing my<em>local</em> environment using<strong>Docker Compose</strong>.</p><p>Not because it’s trendy.</p><p>Because it’s the only way to guarantee I can pull, build, and<em>run</em> my app in under a minute.</p><h2 id="the-indie-dev-reality"><strong>The indie dev reality</strong></h2><p>As solo devs, we move fast. We don’t have infra teams or onboarding docs. Most of our systems live in muscle memory and terminal history.</p><p>That’s fine when you’re in the groove — until you need to:</p><ul><li><p>Revisit a project after a few months.</p></li><li><p>Share it with a collaborator.</p></li><li><p>Spin it up on a new machine.</p></li><li><p>Or just fix a quick bug and realize nothing runs anymore.</p></li></ul><p>A solid local setup is like documentation that actually works.</p><p>Docker Compose is the simplest way to get there.</p><h2 id="why-docker-compose"><strong>Why Docker Compose?</strong></h2><p>It’s not about “microservices” or “container orchestration.” Ignore that stuff.</p><p>Compose is just a YAML file that says:</p><blockquote><p>“Here’s everything my app needs — run it all together.”</p></blockquote><p>You can define your web app, Postgres, and even Supabase’s local stack if you want to mirror production closely.</p><p>When you run docker compose up, everything spins up consistently — same versions, same ports, same config — every time.</p><p>It’s reproducibility for humans.</p><h2 id="the-minimal-example-python--postgres"><strong>The minimal example (Python + Postgres)</strong></h2><p>Let’s say you’re building a FastAPI app that talks to Postgres.</p><p>Here’s a dead-simple docker-compose.yml to make your life easier:</p><figure><a href="https://substackcdn.com/image/fetch/$s_!22g4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/aadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/aadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":1102,"width":1180,"resizeWidth":null,"bytes":167377,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://www.lakshminp.com/i/175771487?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!22g4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 424w, https://substackcdn.com/image/fetch/$s_!22g4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 848w, https://substackcdn.com/image/fetch/$s_!22g4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 1272w, https://substackcdn.com/image/fetch/$s_!22g4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 1456w" sizes="100vw" loading="lazy" width="1180" height="1102"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p><a href="https://gist.github.com/badri/43b709d1a62249464346609a740bb069" rel="external nofollow noopener" class="lnp-link">link to the gist</a></p><p>And a Dockerfile to go along with it.</p><figure><a href="https://substackcdn.com/image/fetch/$s_!-s5u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":1660,"width":1298,"resizeWidth":null,"bytes":314313,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://www.lakshminp.com/i/175771487?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!-s5u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 424w, https://substackcdn.com/image/fetch/$s_!-s5u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 848w, https://substackcdn.com/image/fetch/$s_!-s5u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 1272w, https://substackcdn.com/image/fetch/$s_!-s5u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 1456w" sizes="100vw" loading="lazy" width="1298" height="1660"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p><a href="https://gist.github.com/badri/d198c94da28eae987cc2333b2f168d41" rel="external nofollow noopener" class="lnp-link">Link to the gist</a></p><p>That’s it.</p><p>Now you can run your entire stack with one command:</p><blockquote><p><code>docker compose up</code></p></blockquote><p>Your Python app connects to Postgres instantly.</p><p>No need to brew install, no weird port conflicts, no “is Postgres running?” guessing game.</p><h2 id="want-to-mirror-supabase-locally"><strong>Want to mirror Supabase locally?</strong></h2><p>If you’re using<strong>Supabase</strong> in production but want to run locally, Supabase has its own CLI that uses Docker under the hood.</p><p>You can spin up a near-production clone with:</p><blockquote><p><code>supabase start</code></p></blockquote><p>That’ll run Postgres, API, auth, and storage locally in containers — no manual setup required.</p><p>It’s heavier, but it’s great if you’re testing row-level security, triggers, or anything that depends on Supabase’s stack.</p><h2 id="but-isnt-docker-heavy"><strong>But isn’t Docker heavy?</strong></h2><p>Yeah, a little.</p><p>The first time you pull images, it’ll download a few hundred MB. After that, it’s fast.</p><p>And honestly, the alternative is worse — debugging inconsistent environments and broken local databases.</p><p>The real magic isn’t that it’s fast — it’s that it’s<em>reliable</em>.</p><p>If you take a break from your project for a month, you can come back and it’ll just work.</p><p>That’s worth the disk space.</p><h2 id="bonus-the-same-setup-works-for-production"><strong>Bonus: the same setup works for production</strong></h2><p>Here’s the underrated part — once you have this docker-compose.yml, you’re halfway to a production deployment.</p><p>You can:</p><ul><li><p>Build your app image with docker compose build api.</p></li><li><p>Push it to a registry like Docker Hub or GitHub Container Registry.</p></li><li><p>Deploy it to Fly.io, Render, Railway, or your VPS — all of which happily accept a pre-built Docker image.</p></li></ul><p>That means your<strong>local setup = production setup</strong>.</p><p>No “works on my machine,” no separate Heroku config, no hand-tuned server differences.</p><p>You’re testing<em>exactly</em> what you’ll ship.</p><p>For example, to build your image for deployment:</p><blockquote><p><code>docker compose build api</code></p><p><code>docker tag yourapp_api your-registry.com/yourapp:latest</code></p><p><code>docker push your-registry.com/yourapp:latest</code></p></blockquote><p>Then you can run it anywhere with:</p><blockquote><p><code>docker run -p 8000:8000 your-registry.com/yourapp:latest</code></p></blockquote><p>This alignment — same Dockerfile, same Compose config — is what makes deployment predictable, even as a one-person team.</p><h2 id="quality-of-life-improvements"><strong>Quality-of-life improvements</strong></h2><p>Once you’ve got Compose running smoothly, you can make it even nicer:</p><p><strong>1. Add a Makefile or script for one-command startup:</strong></p><blockquote><p><code>make up</code></p></blockquote><p>Your Makefile contents:</p><blockquote><p><code>up:</code></p><p><code> docker compose up --build</code></p></blockquote><p><strong>2. Add a seed script for your DB:</strong></p><blockquote><p><code>docker compose exec db psql -U dev -d app -f seeds.sql</code></p></blockquote><p><strong>3. Run tests in the same containers:</strong></p><blockquote><p><code>docker compose run api pytest</code></p></blockquote><p>You now have a full, consistent local dev environment that<em>feels like production</em>, without the cloud bill.</p><h2 id="the-point-isnt-docker--its-repeatability"><strong>The point isn’t Docker — it’s repeatability</strong></h2><p>You’re not doing this to “learn containers.”</p><p>You’re doing it because your time is too valuable to waste on setup chores.</p><p>A docker-compose.yml file is the indie dev version of a safety net.</p><p>You can drop your laptop, clone your repo on a new one, and be productive in 60 seconds flat.</p><p>And when it’s time to deploy?</p><p>You’re already 90% there.</p><h2 id="tldr"><strong>TL;DR</strong></h2><ul><li><p>Your SaaS deserves a repeatable local setup.</p></li><li><p>Docker Compose makes it dead simple for Python + Postgres (and Supabase).</p></li><li><p>It doubles as your build foundation for production images.</p></li><li><p>You’ll thank yourself every time you reopen an old project or deploy something new.</p></li></ul><p>It’s one of those rare decisions that’s both practical<em>and</em> future-proof.</p><p>Set it up once — and your dev-to-prod pipeline just became a lot less fragile.</p>
]]></content:encoded></item><item><title>The Kubernetes Controller That Auto-Reloads Your ConfigMaps</title><link>https://lakshminp.com/2025/10/kubernetes-controller-tutorial/</link><pubDate>Tue, 07 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/kubernetes-controller-tutorial/</guid><category>essays</category><category>kubernetes</category><description>Every now and then, you stumble upon a Kubernetes project that makes you stop and think, “Wait, why isn’t this built-in?”
Stakater Reloader is one of those for me.
Here’s the problem it quietly solves: Kubernetes Deployments, DaemonSets, and StatefulSets don’t automatically reload when their ConfigMaps or Secrets change. You could update the config file, roll out a new image, patch the deployment — but the pods? They’ll keep running happily with the old values until you manually restart them. It’s one of those “by design” quirks that has tripped up almost every engineer at least once.</description><content:encoded>&lt;![CDATA[<p>Every now and then, you stumble upon a Kubernetes project that makes you stop and think, “Wait, why isn’t this built-in?”</p><p><a href="https://docs.stakater.com/reloader/" rel="external nofollow noopener" class="lnp-link">Stakater Reloader</a> is one of those for me.</p><p>Here’s the problem it quietly solves: Kubernetes Deployments, DaemonSets, and StatefulSets don’t automatically reload when their ConfigMaps or Secrets change. You could update the config file, roll out a new image, patch the deployment — but the pods? They’ll keep running happily with the old values until you manually restart them. It’s one of those “by design” quirks that has tripped up almost every engineer at least once.</p><p>Reloader fixes that. It’s a lightweight controller that watches for changes in ConfigMaps and Secrets. When it detects one, it simply triggers a rolling restart of the workloads that depend on them. Nothing fancy, nothing hacky — just Kubernetes done right.</p><p>Here’s how it works under the hood. It uses the Kubernetes watch API to monitor resource updates. When a change is observed, it looks for deployments or other workloads annotated with</p><blockquote><p><code>reloader.stakater.com/auto: “true”</code></p></blockquote><p>If it finds one, it patches the deployment’s pod template spec — usually by bumping an annotation — forcing Kubernetes to treat it as a new version and trigger a rolling update. No sidecars, no injection tricks, no external scripts. Just a clean use of the existing control-plane semantics.</p><p>It’s elegant precisely because it doesn’t reinvent anything. It leans into how Kubernetes already works, filling in an obvious usability gap.</p><p>You could argue this is the kind of feature that belongs in<strong>core Kubernetes</strong>. After all, it’s not “extra functionality” — it’s just common sense. If my config changes, my app should refresh. But Kubernetes’ philosophy has always been to stay minimal, leaving operators and tools to extend behavior. That’s how ecosystems like Stakater exist in the first place.</p><p>And yet, Reloader feels different. It doesn’t add complexity; it<em>removes friction</em>. It codifies a best practice we’ve all implemented in ad-hoc ways — shell scripts, kubectl rollout restart, or CI hacks. In a way, Reloader formalizes something that should have been declarative from day one.</p><p>If you look at its implementation, it’s almost deceptively simple — a few controllers, an event handler, and some logic to patch annotations. But simplicity is what makes it beautiful. It’s one of those tools that quietly runs in the background for years without drawing attention — until one day you disable it and everything starts to feel broken again.</p><p>The lesson? Some of the most powerful Kubernetes tools don’t add layers of abstraction; they close tiny gaps that make the system feel humane.</p><p>Reloader doesn’t try to be clever. It just keeps your pods honest.</p>
]]></content:encoded></item><item><title>When DIY Beats Managed Kubernetes</title><link>https://lakshminp.com/2025/09/when-diy-beats-managed-kubernetes/</link><pubDate>Sun, 21 Sep 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/09/when-diy-beats-managed-kubernetes/</guid><category>essays</category><category>kubernetes</category><description>When I first started working with Kubernetes, I immediately gravitated toward managed offerings like EKS, GKE, and AKS. The promise was compelling: let AWS/Google/Azure handle the control plane while you focus on your applications. Fast forward a few years, and I&amp;rsquo;ve come to a somewhat contrarian position—for many teams, especially those with some ops capability, running K3s on virtual machines often makes more sense than using managed Kubernetes.
Let me explain why, and the important caveats to make this approach work.</description><content:encoded>&lt;![CDATA[<p>When I first started working with Kubernetes, I immediately gravitated toward managed offerings like EKS, GKE, and AKS. The promise was compelling: let AWS/Google/Azure handle the control plane while you focus on your applications. Fast forward a few years, and I&rsquo;ve come to a somewhat contrarian position—for many teams, especially those with some ops capability, running K3s on virtual machines often makes more sense than using managed Kubernetes.</p><p>Let me explain why, and the important caveats to make this approach work.</p><h1 id="the-managed-kubernetes-tax">The Managed Kubernetes Tax</h1><p>Managed Kubernetes services aren&rsquo;t free—and I&rsquo;m not just talking about the literal cost (though that&rsquo;s significant). They come with several forms of &ldquo;tax&rdquo;:</p><ol><li><p><strong>Financial cost</strong>: You pay for control plane(s), often per cluster. For small to medium workloads, this can be disproportionately expensive.</p></li><li><p><strong>Complexity tax</strong>: Managed K8s integrates deeply with cloud provider infrastructure—IAM, networking, storage—adding layers of abstraction and potential failure points.</p></li><li><p><strong>Upgrade friction</strong>: Managed K8s upgrades are often more complex than they need to be, involving node group rotations and potential downtime.</p></li><li><p><strong>Cognitive overhead</strong>: You still need to understand Kubernetes, plus the cloud provider&rsquo;s implementation quirks and limitations.</p></li></ol><p>Take EKS, for example. What starts as &ldquo;just let AWS manage the control plane&rdquo; quickly spirals into wrestling with IAM roles for service accounts, custom CNIs, AWS Load Balancer Controllers, and cluster autoscaler configurations that mysteriously stop working after upgrades. I&rsquo;ve spent entire days debugging issues that stemmed from the interaction between EKS and AWS&rsquo;s underlying services—time that could have been spent improving our actual applications.</p><h1 id="enter-k3s-kubernetes-without-the-bloat">Enter K3s: Kubernetes Without the Bloat</h1><p><a href="https://k3s.io/" rel="external nofollow noopener" class="lnp-link">K3s</a> is a certified Kubernetes distribution designed for resource-constrained environments. It&rsquo;s packaged as a single binary under 100MB and uses significantly fewer resources than standard K8s. But don&rsquo;t let the &ldquo;lightweight&rdquo; label fool you—K3s is a production-grade distribution that powers everything from IoT devices to large-scale production systems.</p><p>When deployed on standard VMs (whether AWS EC2, DigitalOcean Droplets, or your own infrastructure), K3s offers several advantages:</p><ol><li><p><strong>Simplicity</strong>: A K3s cluster can be bootstrapped with a single command. No complex cloud provider integration required.</p></li><li><p><strong>Cost efficiency</strong>: Run your entire control plane and worker nodes on standard VMs, often at a fraction of the cost of managed offerings.</p></li><li><p><strong>Portability</strong>: Your setup works the same way regardless of where your VMs are hosted, making multi-cloud and hybrid deployments straightforward.</p></li><li><p><strong>Easier upgrades</strong>: K3s upgrades can be as simple as replacing a binary and restarting a service.</p></li><li><p><strong>Full control</strong>: No mysterious behavior or limitations imposed by the cloud provider&rsquo;s implementation.</p></li></ol><h1 id="the-critical-caveat-you-need-automation">The Critical Caveat: You Need Automation</h1><p>Here&rsquo;s where I need to be clear: this approach only makes sense if you invest in automation. You&rsquo;re essentially building your own management layer, which requires:</p><ol><li><p><strong>Infrastructure as Code</strong>: Your entire VM fleet and K3s deployment should be defined in Terraform, Pulumi, or similar.</p></li><li><p><strong>Automated scaling</strong>: Scripts or tools that can add/remove nodes based on cluster metrics.</p></li><li><p><strong>Upgrade playbooks</strong>: Well-tested procedures for upgrading K3s versions with minimal disruption.</p></li><li><p><strong>Monitoring and alerting</strong>: Comprehensive visibility into both VM and Kubernetes-level metrics.</p></li><li><p><strong>Backup and disaster recovery</strong>: Regular etcd snapshots and documented recovery procedures.</p></li></ol><p>Without these elements, you&rsquo;re likely better off with managed Kubernetes. The goal isn&rsquo;t to recreate every feature of EKS/GKE/AKS, but to build a simpler, more focused system that meets your specific needs.</p><h1 id="real-world-example">Real-World Example</h1><p>For one of my recent projects, we replaced an EKS cluster costing roughly $250/month (control plane + required minimum nodes) with a K3s setup on three small VMs totaling $60/month. The migration took 3 days, and we&rsquo;ve had fewer operational issues since.</p><p>Our automation includes:</p><ul><li><p>Terraform for VM provisioning</p></li><li><p>Ansible for K3s installation and configuration</p></li><li><p>Custom scripts for horizontal scaling based on node resource utilization</p></li><li><p>Prometheus + Grafana for monitoring</p></li><li><p>Weekly etcd snapshots stored in S3</p></li></ul><p>The entire setup is documented in a Git repository, and new team members can spin up a local replica for testing using Vagrant.</p><p>The maintenance complexity with EKS was what ultimately pushed us over the edge. Every few months, AWS would deprecate something or introduce a new &ldquo;recommended&rdquo; way to handle networking, storage, or access control. We&rsquo;d spend days reading through documentation changes and testing upgrades in staging environments. With K3s, upgrades are predictable and focused on Kubernetes itself, not the surrounding ecosystem of AWS-specific components.</p><h1 id="when-to-stick-with-managed-kubernetes">When to Stick with Managed Kubernetes</h1><p>This approach isn&rsquo;t for everyone. You should probably stick with managed Kubernetes if:</p><ol><li><p>You have large, complex clusters with hundreds of nodes</p></li><li><p>Your team has limited operations expertise</p></li><li><p>You need advanced features like managed node auto-scaling groups</p></li><li><p>You&rsquo;re heavily invested in cloud-provider specific features</p></li></ol><h1 id="conclusion">Conclusion</h1><p>The beauty of the K3s-on-VMs approach is that it strips Kubernetes down to what it does best—orchestrating containers—without the added complexity that comes from deep cloud provider integration.</p><p>By building your own lightweight management layer through automation, you get the benefits of Kubernetes with more control, often at a lower cost. The key is being honest about your team&rsquo;s capabilities and needs.</p><p>For startups, indie hackers, and teams that value simplicity and cost-efficiency, this approach is worth considering. You might find that a little investment in automation pays significant dividends in both cost savings and reduced operational complexity.</p><p>Of course, if you enjoy spending your weekends debugging why your EKS cluster suddenly can&rsquo;t talk to your RDS instances despite no apparent changes, then by all means, stick with managed Kubernetes. Some people also enjoy jigsaw puzzles with missing pieces.</p>
]]></content:encoded></item></channel></rss>