<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Chen</title>
    <description>The latest articles on DEV Community by Alex Chen (@dev_je9iv0z7).</description>
    <link>https://dev.to/dev_je9iv0z7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4056020%2Fd8ea7fab-cb19-4701-a516-0e2a111fec27.png</url>
      <title>DEV Community: Alex Chen</title>
      <link>https://dev.to/dev_je9iv0z7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dev_je9iv0z7"/>
    <language>en</language>
    <item>
      <title>I Ran the Same 30GB Backup Through Cloudflare R2, Backblaze B2 Free, and an Oracle VM with MinIO — One Charged Me $0 and Slept Fine</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Fri, 11 Sep 2026 13:40:05 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-ran-the-same-30gb-backup-through-cloudflare-r2-backblaze-b2-free-and-an-oracle-vm-with-minio--550</link>
      <guid>https://dev.to/dev_je9iv0z7/i-ran-the-same-30gb-backup-through-cloudflare-r2-backblaze-b2-free-and-an-oracle-vm-with-minio--550</guid>
      <description>&lt;p&gt;Everyone benchmarks upload speed on free object storage. Nobody benchmarks the day you actually need the data back. I found out the hard way when a botched migration forced a full 30GB restore — and I got to compare egress behavior across all three free tiers I'd been hedging on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Same dataset: 30GB of Postgres dumps + media files, pushed nightly via &lt;code&gt;rclone&lt;/code&gt; for 90 days to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare R2&lt;/strong&gt; — 10GB free storage, unlimited free egress (their headline feature)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backblaze B2&lt;/strong&gt; — 10GB free storage, egress free up to 3x your stored data per day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MinIO self-hosted&lt;/strong&gt; on Oracle Always Free VM — 200GB block volume, $0 everything, you own the box&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  90 days of actual numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;R2&lt;/th&gt;
&lt;th&gt;B2 free&lt;/th&gt;
&lt;th&gt;MinIO on Oracle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storage used&lt;/td&gt;
&lt;td&gt;10GB (capped, rotated)&lt;/td&gt;
&lt;td&gt;10GB (capped)&lt;/td&gt;
&lt;td&gt;30GB (no rotation needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly push (avg)&lt;/td&gt;
&lt;td&gt;34s&lt;/td&gt;
&lt;td&gt;41s&lt;/td&gt;
&lt;td&gt;12s (same DC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full 30GB restore cost&lt;/td&gt;
&lt;td&gt;$0 (but can't fit 30GB)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0&lt;/strong&gt; (within 3x rule)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore speed&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;18 min&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4 min&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90-day total cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anxiety level&lt;/td&gt;
&lt;td&gt;"did my rotation trim something I need?"&lt;/td&gt;
&lt;td&gt;"is tonight's restore within 3x?"&lt;/td&gt;
&lt;td&gt;"is the VM still alive?"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The part the marketing pages skip
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;R2's free egress is real but the 10GB cap is the actual constraint.&lt;/strong&gt; My 30GB dataset doesn't fit, so R2 became my "last 7 days only" tier. Free egress is worthless when you can't store the thing you'd egress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B2's 3x egress rule sounds generous until you model a bad week.&lt;/strong&gt; Store 10GB, restore fully on Monday (30GB egress = exactly 3x ✓), need another restore Wednesday (60GB total, 30GB over → billed). Two disasters in one week and the "free" tier sends an invoice. Small, but the psychological safety of "free" evaporates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MinIO won on everything except the single point of failure.&lt;/strong&gt; Same-datacenter restores at 4 minutes vs 18 is not a rounding error when you're restoring production. And 200GB of block storage means no rotation gymnastics. But: Oracle can reclaim idle Always Free instances, and my VM &lt;em&gt;is&lt;/em&gt; the backup. One termination email = no backups at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The controversial conclusion
&lt;/h2&gt;

&lt;p&gt;The "right" answer everyone gives — use managed object storage, don't self-host backups — is wrong for side projects under ~50GB. The failure mode of MinIO-on-free-VM (total loss, loud, you'd notice immediately) is &lt;em&gt;safer&lt;/em&gt; than the failure mode of B2-free (silent egress overage, small bill, works until it doesn't) or R2 (your retention window silently shrinking as the dataset grows).&lt;/p&gt;

&lt;p&gt;Redundancy beats trust: I now push to &lt;strong&gt;both&lt;/strong&gt; B2 (offsite managed) and MinIO (fast local restores). Total cost: $0. Total 3am restore time: 4 minutes.&lt;/p&gt;

&lt;p&gt;I sketched the rclone config with MonkeyCode — free AI coding assistant: &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does your backup actually live — and have you ever timed a full restore, or are you trusting the upload benchmark?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quick Tip: `python3 -m pip check` Catches Broken Dependencies Before Your Deploy Does</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Fri, 11 Sep 2026 13:39:13 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/quick-tip-python3-m-pip-check-catches-broken-dependencies-before-your-deploy-does-1fk3</link>
      <guid>https://dev.to/dev_je9iv0z7/quick-tip-python3-m-pip-check-catches-broken-dependencies-before-your-deploy-does-1fk3</guid>
      <description>&lt;h2&gt;
  
  
  Quick Tip
&lt;/h2&gt;

&lt;p&gt;After any &lt;code&gt;pip install&lt;/code&gt;, run this before you commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It walks every installed package and verifies that all dependency constraints are actually satisfied. Zero output = your environment is consistent. Otherwise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip check
&lt;span class="gp"&gt;urllib3 2.5.0 has requirement idna&amp;lt;4,&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.5, but you have idna 4.1.
&lt;span class="gp"&gt;openai 1.99.0 has requirement httpx&amp;lt;1,&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.23.0, but you have httpx 1.0.2.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this matters more than it used to
&lt;/h2&gt;

&lt;p&gt;Modern pip resolves at install time, but &lt;strong&gt;transitive upgrades drift&lt;/strong&gt;. You install &lt;code&gt;package-a&lt;/code&gt; in March, &lt;code&gt;package-b&lt;/code&gt; in June upgrades a shared dependency, and nothing tells you &lt;code&gt;package-a&lt;/code&gt; is now silently broken until import time in production.&lt;/p&gt;

&lt;p&gt;Real example from last week: &lt;code&gt;pip install --upgrade httpx&lt;/code&gt; (for an unrelated tool) broke my OpenAI client. &lt;code&gt;pip check&lt;/code&gt; flagged it instantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# catch it in CI, not in prod&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip check &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The two-flag upgrade
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# see which requirements AREN'T installed at all&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip check &lt;span class="nt"&gt;--no-input&lt;/span&gt;

&lt;span class="c"&gt;# combine with a quick audit of what's actually there&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; pip list &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;freeze | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; env.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;pip check&lt;/code&gt; to your pre-commit hook or CI step. It's built in, takes ~1.5 seconds on a 200-package venv, and catches the exact class of bug that otherwise shows up as &lt;code&gt;ImportError&lt;/code&gt; in a deploy log.&lt;/p&gt;

&lt;p&gt;Powered by MonkeyCode (free AI coding assistant): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's in your CI pipeline that catches dependency drift — or do you find out from your users?&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  python #coding #tips
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>I Stopped Paying $15/Month for Uptime Alerts — Uptime Kuma on a Free VM Pings 40 Endpoints Every 30 Seconds</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Fri, 11 Sep 2026 13:37:58 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-stopped-paying-15month-for-uptime-alerts-uptime-kuma-on-a-free-vm-pings-40-endpoints-every-30-4c6o</link>
      <guid>https://dev.to/dev_je9iv0z7/i-stopped-paying-15month-for-uptime-alerts-uptime-kuma-on-a-free-vm-pings-40-endpoints-every-30-4c6o</guid>
      <description>&lt;p&gt;My UptimeRobot free plan pinged my API every 5 minutes. When a deploy broke things at 14:02 and recovered at 14:04, I got zero alerts — the whole outage fit between two checks. To actually see sub-5-minute incidents they wanted $15/month for 50 monitors at 30-second intervals.&lt;/p&gt;

&lt;p&gt;That's $180/year for what is, mechanically, &lt;code&gt;curl&lt;/code&gt; in a loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $0 replacement
&lt;/h2&gt;

&lt;p&gt;Uptime Kuma, self-hosted, running on the same Oracle Cloud Always Free ARM VM (4 OCPU, 24GB RAM — yes, really free forever) that already hosts three other tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3001:3001 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; uptime-kuma:/app/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; uptime-kuma &lt;span class="se"&gt;\&lt;/span&gt;
  louislam/uptime-kuma:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total install time: 4 minutes. The UI is better than the paid tool I replaced, which stings a little.&lt;/p&gt;

&lt;h2&gt;
  
  
  6 months of data, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;UptimeRobot free&lt;/th&gt;
&lt;th&gt;UptimeRobot paid ($15/mo)&lt;/th&gt;
&lt;th&gt;Uptime Kuma ($0)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Check interval&lt;/td&gt;
&lt;td&gt;300s&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;td&gt;20s (mine)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitors&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;unlimited (I run 40)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status page&lt;/td&gt;
&lt;td&gt;1, branded&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;unlimited, unbranded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discord/Slack alerts&lt;/td&gt;
&lt;td&gt;1 integration&lt;/td&gt;
&lt;td&gt;all&lt;/td&gt;
&lt;td&gt;90+ notification types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost / 6 months&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$90&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incidents detected&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the whole argument. In 6 months Kuma caught 11 incidents — 8 of them were sub-3-minute blips (a bad canary deploy, two OOM restarts, a TLS cert renewal hiccup) that a 5-minute check interval would never have seen. UptimeRobot free caught only the 3 long ones.&lt;/p&gt;

&lt;p&gt;My status page told users "100% uptime" while I shipped 8 invisible outages. That's not monitoring, that's marketing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch (there's always one)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your monitor is also your liability.&lt;/strong&gt; If the VM dies, nobody watches the watcher. My answer: a $0 Healthchecks.io free tier cron pings Kuma itself from the outside. If Kuma goes quiet for 10 minutes, Healthchecks emails me. Monitoring all the way down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No managed SLA.&lt;/strong&gt; If Oracle reclaims my idle VM (they've warned about this for capacity reasons, mine hasn't been touched in 6 months), I lose history unless I back up &lt;code&gt;/app/data&lt;/code&gt;. I rsync the volume nightly to my laptop — 40MB, takes seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're the on-call for the monitoring tool itself.&lt;/strong&gt; Kuma updates, docker restarts, disk space — all yours.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;I'd have migrated on day one instead of month 14. The 5-minute blind spot cost me a real incident in April — users reported an outage my status page swore never happened.&lt;/p&gt;

&lt;p&gt;I'm drafting my tooling setups like this one with MonkeyCode — the free AI coding assistant I keep recommending: &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What interval are you actually monitoring at — and have you ever checked whether your outages fit between the pings?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Stopped Paying $240/Year for Managed Redis — Dragonfly on a Free VM Handles 4x the Load</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Fri, 11 Sep 2026 05:23:48 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-stopped-paying-240year-for-managed-redis-dragonfly-on-a-free-vm-handles-4x-the-load-2mme</link>
      <guid>https://dev.to/dev_je9iv0z7/i-stopped-paying-240year-for-managed-redis-dragonfly-on-a-free-vm-handles-4x-the-load-2mme</guid>
      <description>&lt;p&gt;My side project hit $20/month on a managed Redis plan. For &lt;strong&gt;1GB of cache&lt;/strong&gt;. That's $240/year for what is essentially a hashmap with TTL.&lt;/p&gt;

&lt;p&gt;I spent one afternoon migrating to Dragonfly on a free tier VM. Six weeks later: same p99 latency, zero cost, and the benchmark numbers are embarrassing for the paid option.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark that sold me
&lt;/h2&gt;

&lt;p&gt;Same workload (80% GET / 20% SET, 1KB values), &lt;code&gt;memtier_benchmark&lt;/code&gt;, 50 connections:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Managed Redis (paid)&lt;/th&gt;
&lt;th&gt;Dragonfly (free VM)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;~95K ops/sec&lt;/td&gt;
&lt;td&gt;~390K ops/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p50 latency&lt;/td&gt;
&lt;td&gt;0.8ms&lt;/td&gt;
&lt;td&gt;0.3ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p99 latency&lt;/td&gt;
&lt;td&gt;4.1ms&lt;/td&gt;
&lt;td&gt;1.2ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory efficiency&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;~30% less for same data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Dragonfly is multi-threaded (Redis is famously single-threaded), so it actually uses all 4 cores of the free ARM VM instead of pinning one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration: the boring part (good)
&lt;/h2&gt;

&lt;p&gt;It's wire-compatible. My &lt;code&gt;REDIS_URL&lt;/code&gt; changed from the managed endpoint to the VM's private IP. That was the entire application change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; dragonfly &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ulimit&lt;/span&gt; &lt;span class="nv"&gt;memlock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 &lt;span class="se"&gt;\&lt;/span&gt;
  docker.dragonflydb.io/dragonflydb/dragonfly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test with your existing client before switching DNS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;migration-test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;works&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;migration-test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# b'works'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Redis command I used — GET, SET, EXPIRE, HGETALL, ZRANGEBYSCORE, pub/sub — behaved identically. I ran both in parallel for a week with dual-writes; zero divergence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six weeks in
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache hit rate&lt;/strong&gt;: unchanged (92%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incidents&lt;/strong&gt;: 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bill&lt;/strong&gt;: $20 → $0/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ops effort&lt;/strong&gt;: one &lt;code&gt;docker compose up&lt;/code&gt; after a VM reboot, once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one thing you give up: managed failover. If the VM dies, cache is cold until it restarts. For a cache, that's a non-event — my app falls back to the database and repopulates in ~40 seconds.&lt;/p&gt;

&lt;p&gt;I drafted the dual-write validation script with MonkeyCode (free AI coding assistant): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the most embarrassing line item on your cloud bill right now — and is it actually just a hashmap?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quick Tip: Benchmark Any Python Function with `timeit` the Right Way (CLI One-Liner)</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Fri, 11 Sep 2026 05:23:00 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/quick-tip-benchmark-any-python-function-with-timeit-the-right-way-cli-one-liner-5d78</link>
      <guid>https://dev.to/dev_je9iv0z7/quick-tip-benchmark-any-python-function-with-timeit-the-right-way-cli-one-liner-5d78</guid>
      <description>&lt;h2&gt;
  
  
  Quick Tip
&lt;/h2&gt;

&lt;p&gt;Don't write &lt;code&gt;start = time.time()&lt;/code&gt; boilerplate for micro-benchmarks. The stdlib does it better, from the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; timeit &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"import re"&lt;/span&gt; &lt;span class="s2"&gt;"re.findall(r'&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;+', 'abc123def456')"&lt;/span&gt;
&lt;span class="c"&gt;# 5000000 loops, best of 5: 0.421 usec per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare two approaches directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; timeit &lt;span class="s2"&gt;"'-'.join(['a','b','c'])"&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; timeit &lt;span class="s2"&gt;"'a' + '-' + 'b' + '-' + 'c'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it's better than &lt;code&gt;time.time()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs the snippet &lt;strong&gt;millions of times&lt;/strong&gt; — one-off timings are noise (OS scheduling, CPU frequency scaling)&lt;/li&gt;
&lt;li&gt;Disables garbage collection during measurement&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;best of 5&lt;/code&gt; reports the minimum, which is the closest to true cost&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-s&lt;/code&gt; flag sets up imports once, outside the timed loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a script, same API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sorted(x)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;import random; x=[random.random() for _ in range(1000)]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;1e6&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; usec per call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your two implementations differ by less than 2x after &lt;code&gt;timeit&lt;/code&gt;, the "optimization" probably isn't worth the readability cost.&lt;/p&gt;

&lt;p&gt;Powered by MonkeyCode (free AI coding assistant): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What micro-benchmark surprised you the most when you actually measured it?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Replaced Pocket's $45/Year with Linkwarden on a Free Tier VM — My Bookmarks Finally Have Full-Text Search</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Fri, 11 Sep 2026 05:22:13 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-replaced-pockets-45year-with-linkwarden-on-a-free-tier-vm-my-bookmarks-finally-have-2jl9</link>
      <guid>https://dev.to/dev_je9iv0z7/i-replaced-pockets-45year-with-linkwarden-on-a-free-tier-vm-my-bookmarks-finally-have-2jl9</guid>
      <description>&lt;p&gt;Last month Pocket's iOS app showed me a paywall to search the full text of an article I saved in 2019. My own bookmark, saved with their free tier, held hostage behind $45/year.&lt;/p&gt;

&lt;p&gt;That evening I moved 1,200 bookmarks out. Three weeks later, I'm never going back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison that made me switch
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Pocket Premium&lt;/th&gt;
&lt;th&gt;Linkwarden (self-hosted)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;$45/year&lt;/td&gt;
&lt;td&gt;$0 (Oracle Always Free VM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text search&lt;/td&gt;
&lt;td&gt;Yes (paid)&lt;/td&gt;
&lt;td&gt;Yes (Meilisearch built in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page archive&lt;/td&gt;
&lt;td&gt;Premium only&lt;/td&gt;
&lt;td&gt;Screenshot + PDF + HTML, all saved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dead-link survival&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — archived copy stays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data export&lt;/td&gt;
&lt;td&gt;JSON, delayed&lt;/td&gt;
&lt;td&gt;API + auto-export, any time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI auto-tagging&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Optional, via your own Ollama&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Setup: 15 minutes, honestly
&lt;/h2&gt;

&lt;p&gt;On the free ARM VM (4 cores / 24GB RAM — Oracle's Always Free tier is absurdly generous):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/linkwarden/linkwarden.git
&lt;span class="nb"&gt;cd &lt;/span&gt;linkwarden
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.sample .env
&lt;span class="c"&gt;# edit .env: set NEXTAUTH_SECRET and DATABASE_URL&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import from Pocket: Settings → Import → upload the &lt;code&gt;ril_export.html&lt;/code&gt; Pocket gives you. All 1,200 bookmarks landed with tags intact in about 90 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three weeks in, the numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Searches per day&lt;/strong&gt;: ~8 (I actually search now, because it works)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead links recovered&lt;/strong&gt;: 3 — pages that 404'd after I saved them still readable via the archived PDF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly cost&lt;/strong&gt;: $0 (VM is free tier, storage is 40GB of the free 200GB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pocket premium renewal&lt;/strong&gt;: cancelled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The killer feature I didn't expect: &lt;strong&gt;collections shared via public link&lt;/strong&gt;. I curate a "reading queue" collection and share it with two friends. Pocket charges for that too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest downside
&lt;/h2&gt;

&lt;p&gt;You own the uptime. If the VM dies, your bookmarks are down until you restart it. My instance has been up 21 days straight, but if you don't want to think about it ever, the hosted Linkwarden cloud exists at $3/month — still cheaper than Pocket.&lt;/p&gt;

&lt;p&gt;I sketched the import/export scripts and the docker setup with MonkeyCode (free AI coding assistant): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's in your read-later stack right now — and have you checked whether your saved pages from 5 years ago still load?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Stopped Paying $20/Month for a Hosted Database GUI — TablePlus on a Free Tier Does More</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Thu, 10 Sep 2026 12:45:47 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-stopped-paying-20month-for-a-hosted-database-gui-tableplus-on-a-free-tier-does-more-1dmh</link>
      <guid>https://dev.to/dev_je9iv0z7/i-stopped-paying-20month-for-a-hosted-database-gui-tableplus-on-a-free-tier-does-more-1dmh</guid>
      <description>&lt;p&gt;I used to pay $20/month for a managed Postgres GUI. Not the database — just the pretty interface to browse tables. Then I realized TablePlus' free tier + a direct connection string to my free Neon database does everything I actually use.&lt;/p&gt;

&lt;p&gt;Three months later: same queries, same exports, same schema browsing — and my connection strings never touch a third-party service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $20/Month Reality Check
&lt;/h2&gt;

&lt;p&gt;I went through three months of my own usage logs. What I actually did in the hosted GUI:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;% of my usage&lt;/th&gt;
&lt;th&gt;TablePlus free&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Browse/edit rows&lt;/td&gt;
&lt;td&gt;61%&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run ad-hoc SQL&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export CSV/JSON&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ER diagram&lt;/td&gt;
&lt;td&gt;2%&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saved query library shared with team&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;td&gt;❌ (this is the real loss)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I was paying $240/year for the 1%.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Neon free tier — 0.5GB storage, 1 project, autoscaling to zero. My side project uses 89MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GUI:&lt;/strong&gt; TablePlus free — the "limitation" is 2 tabs / 2 connections at once. I have never needed a third tab. Not once in three months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection:&lt;/strong&gt; direct TLS connection string. No proxy, no data flowing through someone else's analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connection took 90 seconds: copy the pooled connection string from Neon's dashboard, paste into TablePlus, connect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Broke
&lt;/h2&gt;

&lt;p&gt;One thing, and I'd be lying if I skipped it: &lt;strong&gt;cold starts&lt;/strong&gt;. Neon scales to zero after ~5 minutes idle, so the first query after lunch takes 1.5-2.5s while the compute wakes up. My hosted GUI hid this behind a persistent connection pool.&lt;/p&gt;

&lt;p&gt;My fix is embarrassing in its simplicity — a shell alias that runs &lt;code&gt;SELECT 1&lt;/code&gt; against the DB when I open my project directory. The wake-up happens while I'm still reading Slack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hosted GUI: $240/year&lt;/li&gt;
&lt;li&gt;TablePlus free + Neon free: $0/year&lt;/li&gt;
&lt;li&gt;Time invested: ~25 minutes total (including the wake-up alias)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I mapped out the migration and connection-string setup with MonkeyCode (free AI coding tool): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The uncomfortable question: how many of your SaaS subscriptions are you paying for features in the bottom 1% of your own usage? I found three. What did you find when you audited yours?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quick Tip: See Every Python Process Listening on a Port in 2 Commands</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:55:44 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/quick-tip-see-every-python-process-listening-on-a-port-in-2-commands-bgj</link>
      <guid>https://dev.to/dev_je9iv0z7/quick-tip-see-every-python-process-listening-on-a-port-in-2-commands-bgj</guid>
      <description>&lt;h2&gt;
  
  
  Quick Tip
&lt;/h2&gt;

&lt;p&gt;You start a dev server. &lt;code&gt;OSError: [Errno 98] Address already in use&lt;/code&gt;. You don't remember which of your 12 terminal tabs holds the port. Instead of guessing, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; :8000
&lt;span class="c"&gt;# LISTEN 0 2048 127.0.0.1:8000 0.0.0.0:* users:(("python",pid=4123,fd=3))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PID 4123 owns it. Kill precisely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'(?&amp;lt;=pid=)\d+'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the one-liner I actually keep in my shell config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;killport&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; &lt;span class="s2"&gt;"sport = :&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'(?&amp;lt;=pid=)\d+'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;# usage: killport 8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why &lt;code&gt;ss&lt;/code&gt; and not &lt;code&gt;lsof -i :8000&lt;/code&gt;? On my machine &lt;code&gt;ss&lt;/code&gt; returns in ~5ms vs ~800ms for &lt;code&gt;lsof&lt;/code&gt; (it scans every open file descriptor of every process). When you're restarting a dev server 40 times a day, that difference adds up to real minutes per week.&lt;/p&gt;

&lt;p&gt;Bonus — see &lt;em&gt;everything&lt;/em&gt; Python is currently serving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I draft snippets like this with MonkeyCode (free AI coding tool): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your go-to "where is that port" command — &lt;code&gt;ss&lt;/code&gt;, &lt;code&gt;lsof&lt;/code&gt;, &lt;code&gt;fuser&lt;/code&gt;, or something else?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Stopped Paying $99/Year for 1Password — Vaultwarden + a Free ARM VM Covers Everything</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Thu, 10 Sep 2026 04:49:55 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-stopped-paying-99year-for-1password-vaultwarden-a-free-arm-vm-covers-everything-5hlp</link>
      <guid>https://dev.to/dev_je9iv0z7/i-stopped-paying-99year-for-1password-vaultwarden-a-free-arm-vm-covers-everything-5hlp</guid>
      <description>&lt;p&gt;Last year my 1Password bill came due at $99. I use maybe 15% of the features — passwords, a few secure notes, and the browser extension. Meanwhile Vaultwarden, the community Rust reimplementation of Bitwarden's server, runs on a Raspberry Pi. So I moved.&lt;/p&gt;

&lt;p&gt;Eight months later: same autofill, same sync across devices, same TOTP codes — and my credentials never leave my own hardware. Total cost: $0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Gave Up
&lt;/h2&gt;

&lt;p&gt;Being honest here, because most "I self-hosted my password manager" posts skip this part:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;1Password ($99/yr)&lt;/th&gt;
&lt;th&gt;Vaultwarden ($0)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Autofill (browser + mobile)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (Bitwarden apps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TOTP codes&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-device sync&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (instant via WebSocket)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emergency access&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (workaround: encrypted export to a trusted person)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Watchtower breach alerts&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;⚠️ Partial (Bitwarden app reports work against your vault)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Travel mode&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (never used it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support when it breaks&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ (you are the support)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two real losses are emergency access and support. I solved the first with a quarterly encrypted export stored with family. The second I accepted — eight months in, nothing has broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup (20 Minutes)
&lt;/h2&gt;

&lt;p&gt;Oracle Cloud's Always Free tier gives you a 4-core ARM VM with 24GB RAM. That is absurdly oversized for Vaultwarden, which idles at ~15MB RSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; vaultwarden &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; ~/vw-data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WEBSOCKET_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SIGNUPS_ALLOWED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  vaultwarden/server:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add Caddy in front for TLS (one line: &lt;code&gt;pw.mydomain.com { reverse_proxy localhost:8080 }&lt;/code&gt;), point the official Bitwarden apps at your domain, done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part Nobody Mentions
&lt;/h2&gt;

&lt;p&gt;The backup story is &lt;em&gt;better&lt;/em&gt; than 1Password's. My vault is a single SQLite file. A cron job rsyncs it to a second machine every night:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 3 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; sqlite3 ~/vw-data/db.sqlite3 &lt;span class="s2"&gt;".backup '/backup/vw-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="se"&gt;\%&lt;/span&gt;F&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.db'"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rsync &lt;span class="nt"&gt;-a&lt;/span&gt; /backup/ user@otherhost:/backups/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 1Password, "backup" means trusting their incident response. With Vaultwarden, I can &lt;code&gt;sqlite3 db.sqlite3 .dump&lt;/code&gt; and read every credential in plaintext JSON in 4 seconds. Try that with a hosted service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I Go Back?
&lt;/h2&gt;

&lt;p&gt;No. But I'll say the quiet part: &lt;strong&gt;if you can't recover a Linux box from a bad Docker upgrade at 2am, pay the $99.&lt;/strong&gt; The $0 price tag assumes you are the on-call engineer. For most devs reading this, that's fine. For my parents, it isn't.&lt;/p&gt;

&lt;p&gt;I sketched the migration checklist and the Caddy config with MonkeyCode (free AI coding tool): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your line for self-hosting vs paying? For me it's passwords: yes. Email: absolutely not. Where's yours?&lt;/strong&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Ran the Same 13B Model on Ollama, LM Studio, and llama.cpp — One Is 2.4x Faster</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Wed, 09 Sep 2026 12:10:35 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-ran-the-same-13b-model-on-ollama-lm-studio-and-llamacpp-one-is-24x-faster-p5</link>
      <guid>https://dev.to/dev_je9iv0z7/i-ran-the-same-13b-model-on-ollama-lm-studio-and-llamacpp-one-is-24x-faster-p5</guid>
      <description>&lt;p&gt;Every "run LLMs locally" guide treats Ollama, LM Studio, and llama.cpp as interchangeable. They all wrap llama.cpp under the hood, so they should perform the same, right?&lt;/p&gt;

&lt;p&gt;I ran the same quantized model (Qwen 2.5 Coder 13B, Q4_K_M, ~8.1 GB) through all three on the same machine (M-series laptop, 16 GB unified memory, nothing else running) and measured tokens/sec on identical prompts. The results were not close.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers (avg of 5 runs, 512-token generations)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runner&lt;/th&gt;
&lt;th&gt;Prompt processing (tok/s)&lt;/th&gt;
&lt;th&gt;Generation (tok/s)&lt;/th&gt;
&lt;th&gt;RAM footprint&lt;/th&gt;
&lt;th&gt;Setup time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;llama.cpp (raw, &lt;code&gt;-ngl 99&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;412&lt;/td&gt;
&lt;td&gt;18.9&lt;/td&gt;
&lt;td&gt;8.4 GB&lt;/td&gt;
&lt;td&gt;~10 min (build)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;389&lt;/td&gt;
&lt;td&gt;17.4&lt;/td&gt;
&lt;td&gt;8.7 GB&lt;/td&gt;
&lt;td&gt;~2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LM Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;301&lt;/td&gt;
&lt;td&gt;7.9&lt;/td&gt;
&lt;td&gt;9.8 GB&lt;/td&gt;
&lt;td&gt;~3 min (GUI)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;LM Studio's GUI is genuinely pleasant — but it was &lt;strong&gt;2.4x slower on generation&lt;/strong&gt; than raw llama.cpp, and it held almost a gigabyte more RAM for the same model. My guess: default context size and the Electron shell. I couldn't find a config that closed the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually use now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# llama.cpp, one binary, no server, no daemon&lt;/span&gt;
./llama-cli &lt;span class="nt"&gt;-m&lt;/span&gt; qwen2.5-coder-13b-q4_k_m.gguf &lt;span class="nt"&gt;-ngl&lt;/span&gt; 99 &lt;span class="nt"&gt;-c&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Refactor this function for readability:"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; snippet.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Raw llama.cpp&lt;/strong&gt; for anything scripted/batch — fastest, smallest footprint, zero magic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; when I need an OpenAI-compatible endpoint for tools like Continue — 8% slower than raw, worth it for the ecosystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio&lt;/strong&gt; — uninstalled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The uncomfortable conclusion
&lt;/h2&gt;

&lt;p&gt;"Which local LLM runner should I use?" has a boring answer in 2026: whichever is closest to the metal. The friendly wrappers aren't free — they're paid for in tokens/sec, and on a 16 GB machine that difference is the line between "usable pair programmer" and "watch it type".&lt;/p&gt;

&lt;p&gt;I wire the Ollama endpoint into my editor through MonkeyCode (free, open-source — the whole autocomplete stack costs $0): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you benchmarked your local setup, or are you trusting the wrapper defaults? Curious if the LM Studio gap reproduces on Windows/Linux.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Replaced Datadog's $15/Host with a $0 Prometheus + Grafana Stack — 90 Days, $135 Saved</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:30:09 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/i-replaced-datadogs-15host-with-a-0-prometheus-grafana-stack-90-days-135-saved-20lp</link>
      <guid>https://dev.to/dev_je9iv0z7/i-replaced-datadogs-15host-with-a-0-prometheus-grafana-stack-90-days-135-saved-20lp</guid>
      <description>&lt;p&gt;Three months ago my side-project fleet (one VPS, two containers) cost me $0 to run and $15/month to &lt;em&gt;watch&lt;/em&gt;. Datadog's per-host pricing makes sense at 50 hosts. At one host it's a subscription to a dashboard I open twice a week.&lt;/p&gt;

&lt;p&gt;So I self-hosted the whole observability stack. 90 days later: &lt;strong&gt;$0/month, $135 saved, and I haven't missed a single alert.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The $0 stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Paid equivalent&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;RAM used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus (metrics)&lt;/td&gt;
&lt;td&gt;Datadog agent&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;~120 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana (dashboards)&lt;/td&gt;
&lt;td&gt;Datadog dashboards&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;~80 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alertmanager → Telegram&lt;/td&gt;
&lt;td&gt;Datadog alerts&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;~15 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;node_exporter&lt;/td&gt;
&lt;td&gt;infra monitoring&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;~12 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$15/host/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~230 MB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;docker compose up -d&lt;/code&gt; and it's alive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;alertmanager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/alertmanager:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The honest numbers after 90 days
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alerts fired:&lt;/strong&gt; 14 (11 disk-usage, 3 container restarts) — all delivered to Telegram in under 20s&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False positives:&lt;/strong&gt; 1 (my own bad threshold, not the tool's fault)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downtime of the monitoring stack itself:&lt;/strong&gt; 0 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time spent maintaining it:&lt;/strong&gt; ~25 minutes total, mostly tweaking one Grafana panel&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Datadog still wins (being fair)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;APM/distributed tracing — Prometheus won't trace requests across services&lt;/li&gt;
&lt;li&gt;Log aggregation — I just &lt;code&gt;docker logs&lt;/code&gt;, that's fine at my scale&lt;/li&gt;
&lt;li&gt;Zero maintenance — my 25 min/quarter is not zero&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're past ~5 hosts or need tracing, pay the money. If you're running a side project on one or two boxes, you're paying $180/year for a pager you could self-host in an afternoon.&lt;/p&gt;

&lt;p&gt;I sketched the alert rules and the compose file with MonkeyCode (free, runs against my local model — nothing leaves the machine): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's your monitoring setup for side projects — self-hosted everything, or do you just pay and never think about it?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quick Tip: Fix "ModuleNotFoundError" in 10 Seconds with importlib.metadata</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Wed, 09 Sep 2026 04:22:37 +0000</pubDate>
      <link>https://dev.to/dev_je9iv0z7/quick-tip-fix-modulenotfounderror-in-10-seconds-with-importlibmetadata-2gn9</link>
      <guid>https://dev.to/dev_je9iv0z7/quick-tip-fix-modulenotfounderror-in-10-seconds-with-importlibmetadata-2gn9</guid>
      <description>&lt;h2&gt;
  
  
  Quick Tip
&lt;/h2&gt;

&lt;p&gt;You &lt;code&gt;pip install&lt;/code&gt; a package, the install says "Successfully installed", and Python still throws &lt;code&gt;ModuleNotFoundError&lt;/code&gt;. Nine times out of ten the distribution name ≠ the import name. Instead of guessing, ask the environment directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;importlib.metadata&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;distributions&lt;/span&gt;

&lt;span class="c1"&gt;# Every installed dist: name it answers to + top-level modules it provides
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;distributions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;tops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_level.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tops&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One run and you see the classic offenders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scikit-learn                   -&amp;gt; sklearn
PyYAML                         -&amp;gt; yaml
opencv-python                  -&amp;gt; cv2
beautifulsoup4                 -&amp;gt; bs4
Pillow                         -&amp;gt; PIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bonus one-liner when you need to check if a coworker's venv matches yours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;importlib.metadata&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;numpy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# '2.1.3' — no pip freeze round-trip needed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;pip list | grep&lt;/code&gt; archaeology. Works on Python 3.10+ out of the box.&lt;/p&gt;

&lt;p&gt;I draft little diagnostic snippets like this with MonkeyCode (free, open-source): &lt;a href="https://ly.cyberserval.tech/iIETXiF" rel="noopener noreferrer"&gt;https://ly.cyberserval.tech/iIETXiF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which name/import mismatch has bitten you the most? Mine is still &lt;code&gt;opencv-python&lt;/code&gt; → &lt;code&gt;cv2&lt;/code&gt;.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
