<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>John Nunemaker</title><description>Thoughts on building software and software businesses. Owner of Box Out Sports and Very Good Software (Fireside, Flipper). Previously GitHub. Creator of HTTParty, Flipper, and more.</description><link>https://www.johnnunemaker.com/</link><language>en-us</language><atom:link href="https://www.johnnunemaker.com/rss.xml" rel="self" type="application/rss+xml"/><item><title>The Friday Deploy: Your PR Shouldn&apos;t Make Your Stomach Hurt</title><link>https://www.johnnunemaker.com/the-friday-deploy-your-pr-shouldnt-make-your-stomach-hurt/</link><guid isPermaLink="true">https://www.johnnunemaker.com/the-friday-deploy-your-pr-shouldnt-make-your-stomach-hurt/</guid><description>Garrett and I talk small PRs, bounded AI reviews, and moving Fireside&apos;s file storage with feature flags. Quiet support is nice. Proof that things work is better.</description><pubDate>Fri, 06 Nov 2026 15:30:00 GMT</pubDate><content:encoded>&lt;p&gt;Garrett and I recorded &lt;a href=&quot;https://thefridaydeploy.fireside.fm/2&quot;&gt;episode two of The Friday Deploy&lt;/a&gt;. We started with personalized interfaces and ended with a basketball app and me casually mentioning that I’d rewritten a chunk of Flare to run on Cloudflare. As one does.&lt;/p&gt;
&lt;iframe src=&quot;https://player.fireside.fm/v2/wKHjBfwC+Ii7KYZXS&quot; title=&quot;Episode 2: Your PR Shouldn&apos;t Make Your Stomach Hurt&quot; loading=&quot;lazy&quot; style=&quot;aspect-ratio: auto; height: 130px; color-scheme: light;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;But the thing I keep coming back to is the pull request that makes your stomach hurt.&lt;/p&gt;
&lt;p&gt;I’ve made plenty of those lately. Ask AI to build something. Have another model review it. Fix the findings. Review it again. Find more things. Fix those. Pretty soon the little change you wanted is 3,000 lines and you don’t want to merge any of it.&lt;/p&gt;
&lt;p&gt;For a while, my response was to keep going. Surely one more review would make me feel better.&lt;/p&gt;
&lt;p&gt;Sometimes the better answer is to throw it away and ask for something smaller.&lt;/p&gt;
&lt;h2 id=&quot;put-an-end-on-the-review&quot;&gt;Put an End on the Review&lt;/h2&gt;
&lt;p&gt;I &lt;a href=&quot;https://www.johnnunemaker.com/dont-make-a-human-do-what-ai-is-amazing-at/&quot;&gt;wrote up the Nunebot process already&lt;/a&gt;, so I won’t repeat the whole thing here. The detail that helped most: after the first review, review the fixes, not the entire change again. Bound the loop. If it’s still a mess, stop.&lt;/p&gt;
&lt;p&gt;Garrett came at the same problem from the UI side. His Workbench process runs deterministic checks, gets other models to look for holes, and tracks findings against the state of the code where they were found. Each finding needs a decision. Fix it, dismiss it, or bring it to him. Don’t just keep rediscovering the same thing forever.&lt;/p&gt;
&lt;p&gt;We took different paths, but ended up wanting the same thing: a small change with enough proof that merging it feels obvious.&lt;/p&gt;
&lt;h2 id=&quot;quiet-support-isnt-proof&quot;&gt;Quiet Support Isn’t Proof&lt;/h2&gt;
&lt;p&gt;This came up while we were talking about moving Fireside’s file storage to Cloudflare R2.&lt;/p&gt;
&lt;p&gt;Support was quiet. Which is great! Except when you’re changing something important and start wondering if it’s quiet because everything works or because nobody has tried the thing yet.&lt;/p&gt;
&lt;p&gt;So I asked the agent to watch more than errors. Successful MP3 uploads. Images. Transcripts. Cloudflare errors. Honeybadger. Support requests. Tell me what’s actually happening while we roll this out.&lt;/p&gt;
&lt;p&gt;It started with short check-ins and backed off as we gained confidence. We weren’t just waiting for somebody to tell us we’d broken their podcast.&lt;/p&gt;
&lt;p&gt;That distinction feels important. No errors is one signal. People successfully doing the thing you changed is another.&lt;/p&gt;
&lt;h2 id=&quot;two-flags-two-decisions&quot;&gt;Two Flags, Two Decisions&lt;/h2&gt;
&lt;p&gt;We separated R2 reads from R2 writes.&lt;/p&gt;
&lt;p&gt;First, read from R2 while the old storage was still receiving writes and replicating them over. Get comfortable with that. Then switch writes.&lt;/p&gt;
&lt;p&gt;The flags gave us smaller decisions instead of one giant moment where everything had to change at once. They also made us be specific about rollback. Switching writes back while leaving R2 reads on was one thing. Switching reads back after new files had landed only in R2 would mean copying those files back first.&lt;/p&gt;
&lt;p&gt;A flag is useful. Knowing what the flag can safely undo is useful too.&lt;/p&gt;
&lt;p&gt;One version of this work wanted another database column to track the switch. I wasn’t convinced we needed it. Starting over with a simpler approach helped get the implementation back to something I felt good about.&lt;/p&gt;
&lt;h2 id=&quot;more-of-the-fun-stuff&quot;&gt;More of the Fun Stuff&lt;/h2&gt;
&lt;p&gt;My favorite part of the conversation was Garrett talking about the iOS app he’s building to help analyze basketball shooting mechanics.&lt;/p&gt;
&lt;p&gt;He coaches. He likes basketball, information design, and math. Now he can put those things together without first taking a detour through learning an entirely new platform. He’s going to the gym, recording shots, testing with other people, and iterating.&lt;/p&gt;
&lt;p&gt;He still runs reviews and tests. He’s still making decisions. He’s just spending more of his time on the parts he enjoys.&lt;/p&gt;
&lt;p&gt;That’s the bit I’m excited about too. Smaller changes. Better evidence. Less time wrestling with a PR I don’t want to merge.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://thefridaydeploy.fireside.fm/2&quot;&gt;Listen to episode two&lt;/a&gt;, or &lt;a href=&quot;https://www.johnnunemaker.com/the-friday-deploy/&quot;&gt;start with episode one&lt;/a&gt;. You can subscribe on &lt;a href=&quot;https://podcasts.apple.com/us/podcast/the-friday-deploy/id6816197157&quot;&gt;Apple Podcasts&lt;/a&gt;, &lt;a href=&quot;https://open.spotify.com/show/4tplJ47HVq7Yn4o07zjQzI&quot;&gt;Spotify&lt;/a&gt;, or &lt;a href=&quot;https://feeds.fireside.fm/thefridaydeploy/rss&quot;&gt;RSS&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>podcast</category><category>ai</category><category>flipper</category><category>fireside</category></item><item><title>Don&apos;t Make a Human Do What AI Is Amazing At</title><link>https://www.johnnunemaker.com/dont-make-a-human-do-what-ai-is-amazing-at/</link><guid isPermaLink="true">https://www.johnnunemaker.com/dont-make-a-human-do-what-ai-is-amazing-at/</guid><description>Tiny PRs, irrefutable proof, and reviewing only the fixes. How Nunebot makes my apps a little better every day without me doing the tedious parts.</description><pubDate>Thu, 29 Oct 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last weekend we were camping. Fall break. Glorious northern Indiana weather. My kids were fishing with grandpa, so I went for a walk around the campground (to check out the other rigs as dads do). Somewhere along the way I opened &lt;a href=&quot;https://t3.codes&quot;&gt;T3&lt;/a&gt; and told Nunebot to do another run. Merge, merge, merge, run it again, merge, merge. Just getting some exercise and enjoying the fall weather.&lt;/p&gt;
&lt;p&gt;Between Saturday and Sunday I merged 113 pull requests. Definitely the most in a two day span in the two decades I’ve been doing this.&lt;/p&gt;
&lt;p&gt;I know how that sounds. But I honestly like this more than games, mindless scrolling, or Instagram. It’s relaxing for me. Read a PR, it makes sense, merge. Little dopamine hit. Next.&lt;/p&gt;
&lt;p&gt;I’ve explained Nunebot to a handful of people now, and every time I think, why am I not writing this down? So here it is. When I lay it all out, it sounds stupidly obvious. But a couple of the details took me a while to figure out, and those are the parts that make it work.&lt;/p&gt;
&lt;h2 id=&quot;what-nunebot-is&quot;&gt;What Nunebot Is&lt;/h2&gt;
&lt;p&gt;Nunebot is &lt;a href=&quot;https://gist.github.com/jnunemaker/e421523d2508716450d41a5d785e48ce&quot;&gt;a prompt&lt;/a&gt;. That’s it. It’s a markdown file in a private git repo I call brain. I tell an agent to read it and run it, and it makes a pass over my repos in priority order. For each repo it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tends the PRs it opened on earlier runs.&lt;/li&gt;
&lt;li&gt;Gathers context.&lt;/li&gt;
&lt;li&gt;Picks one change that’s an obvious yes.&lt;/li&gt;
&lt;li&gt;Builds it in its own worktree.&lt;/li&gt;
&lt;li&gt;Runs &lt;a href=&quot;https://code.claude.com/docs/en/commands&quot;&gt;&lt;code&gt;/simplify&lt;/code&gt;&lt;/a&gt; on it, then &lt;a href=&quot;https://github.com/garrytan/gstack/tree/main/qa&quot;&gt;&lt;code&gt;/qa&lt;/code&gt;&lt;/a&gt;, and finally &lt;a href=&quot;https://github.com/garrytan/gstack/tree/main/codex&quot;&gt;&lt;code&gt;/codex review&lt;/code&gt;&lt;/a&gt; (with a few rounds of fixes at most).&lt;/li&gt;
&lt;li&gt;Proves it works.&lt;/li&gt;
&lt;li&gt;Opens a PR labeled &lt;code&gt;nunebot&lt;/code&gt; and gets CI green.&lt;/li&gt;
&lt;li&gt;Writes down what it did in brain.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It never merges, deploys, or changes anything in production (yet). I merge. Most of my apps deploy on merge, so that’s the whole job for me for these types of changes.&lt;/p&gt;
&lt;p&gt;Why the name? I needed something, and Nunebot is funny.&lt;/p&gt;
&lt;h2 id=&quot;why-i-built-it&quot;&gt;Why I Built It&lt;/h2&gt;
&lt;p&gt;The goal is &lt;a href=&quot;https://en.wikipedia.org/wiki/Kaizen&quot;&gt;kaizen&lt;/a&gt;. Small improvements to every app, all the time, without me ever doing the tedious stuff AI is better at anyway. Three things drove it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The tedium.&lt;/strong&gt; I’m not trying to get it to build all my features. What I want is to never update a dependency again. I want to never fix a &lt;a href=&quot;https://www.honeybadger.io&quot;&gt;Honeybadger&lt;/a&gt; error by copying it into an agent and saying “fix this” (or by using their great MCP).&lt;/p&gt;
&lt;p&gt;I don’t want to remember to run a GEO audit, an SEO audit, or a security audit. My teammates and I were doing all of this chore work by hand across &lt;a href=&quot;https://boxoutsports.com&quot;&gt;Box Out&lt;/a&gt;, &lt;a href=&quot;https://fireside.fm&quot;&gt;Fireside&lt;/a&gt;, &lt;a href=&quot;https://flippercloud.io&quot;&gt;Flipper&lt;/a&gt;, and &lt;a href=&quot;https://flare.am&quot;&gt;Flare&lt;/a&gt;. I want all of it to run all the time and just pop up PRs. And I just say yep, yep, yep, yep, yep. Then it goes again.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Getting better without me.&lt;/strong&gt; None of these PRs moves the needle on its own. That’s exactly why they never got done. But all together? Performance problems get fixed faster. Security holes get closed faster. Customers are happier because things just work. The marketing keeps getting better whether I’m paying attention or not. The prompt opens with the framing I care about most:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Software is not a factory. A factory builds identical widgets to a fixed spec and is done. Software changes every day: customers, scale, and requirements shift, so a perfect up-front spec never exists. Treat each repo like a painting you come back to: make one deliberate improvement per pass, and leave it better than you found it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Letting AI do what it’s great at.&lt;/strong&gt; AI is amazing at a tight, focused, 40 line change with a test that proves it. It’s a lot less amazing at 3,000 line PRs. I know because I kept making them. I’d keep looping review and fix, trying to get to zero findings. Sometimes it never got there. It just kept finding things, the code kept getting more complicated, and pretty soon I had a 3,000 line PR that &lt;strong&gt;made my stomach hurt&lt;/strong&gt; and &lt;strong&gt;I was afraid to merge&lt;/strong&gt;. I never want to be there again. So the whole thing is designed around small.&lt;/p&gt;
&lt;h2 id=&quot;step-one-give-it-all-the-context&quot;&gt;Step One: Give It All the Context&lt;/h2&gt;
&lt;p&gt;Nunebot can’t do much if it can’t see what I see. So the first thing is MCPs for everything that tells me what’s happening in production: &lt;a href=&quot;https://www.honeybadger.io&quot;&gt;Honeybadger&lt;/a&gt; for errors, &lt;a href=&quot;https://flare.am&quot;&gt;Flare&lt;/a&gt; for performance, &lt;a href=&quot;https://planetscale.com&quot;&gt;PlanetScale&lt;/a&gt; query insights, &lt;a href=&quot;https://crisp.chat&quot;&gt;Crisp&lt;/a&gt;, &lt;a href=&quot;https://www.helpscout.com&quot;&gt;Help Scout&lt;/a&gt;, and &lt;a href=&quot;https://letsjelly.com&quot;&gt;Jelly&lt;/a&gt; for support across the apps, plus &lt;a href=&quot;https://www.cloudflare.com&quot;&gt;Cloudflare&lt;/a&gt; for costs, the &lt;a href=&quot;https://github.com/linode/linode-cli&quot;&gt;Linode CLI&lt;/a&gt; for the servers behind my personal stuff and Fireside, and the &lt;a href=&quot;https://devcenter.heroku.com/articles/heroku-cli&quot;&gt;Heroku CLI&lt;/a&gt; for Box Out and Flipper Cloud (for now).&lt;/p&gt;
&lt;p&gt;The second thing is knowing what I care about. That’s brain. It’s a private repo full of markdown: a short &lt;strong&gt;Now&lt;/strong&gt; list, ranked priorities, a file per project, an inbox for random ideas, a list of things only I can do, and a folder for Nunebot. It’s unstructured and wildly inefficient. &lt;a href=&quot;https://justin.searls.co&quot;&gt;Justin Searls&lt;/a&gt; told me I should use SQLite with Litestream so all my computers share the same database, and he’s probably right. But I already had the repo, agents already know how to commit, and it works. I’ll get this tuned and swap out the component later.&lt;/p&gt;
&lt;p&gt;Brain is the part that matters most. Nunebot reads it first, and if brain has anything for a repo that a PR can solve or move forward, it works on that, even when something else looks easier or more interesting.&lt;/p&gt;
&lt;h2 id=&quot;step-two-decide-what-to-do&quot;&gt;Step Two: Decide What to Do&lt;/h2&gt;
&lt;p&gt;The repos are in priority order. Revenue first: Box Out (it makes the most) and Paint (Box Out’s renderer), then Flipper Cloud, &lt;a href=&quot;https://github.com/flippercloud/flipper&quot;&gt;Flipper&lt;/a&gt;, Flipper Pro, and the Flare repos, then Fireside. Garrett is primary on Fireside, so I just do little things there when I have extra tokens. Then everything else: &lt;a href=&quot;https://pitchin.now&quot;&gt;Pitch In Now&lt;/a&gt;, &lt;a href=&quot;https://preachindex.com&quot;&gt;PreachIndex&lt;/a&gt;, &lt;a href=&quot;https://github.com/jnunemaker/httparty&quot;&gt;HTTParty&lt;/a&gt;, &lt;a href=&quot;https://southbendwatch.co&quot;&gt;South Bend Watch Co&lt;/a&gt;, &lt;a href=&quot;https://github.com/jnunemaker/workspace&quot;&gt;workspace&lt;/a&gt;, this site, and so on.&lt;/p&gt;
&lt;p&gt;When brain has nothing for a repo, it looks everywhere else:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cheap stuff, every run.&lt;/strong&gt; The repo’s &lt;code&gt;docs/backlog&lt;/code&gt; (markdown files of things we want to do, with the research and context ready for when we pull the trigger, like a fleshed-out GitHub issue that lives in the repo), GitHub issues, support conversations, Honeybadger, metrics, and a quick dead code scan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saved findings.&lt;/strong&gt; Every audit’s results land in brain, one file per repo (&lt;code&gt;nunebot/findings/&amp;lt;repo&amp;gt;.md&lt;/code&gt;). Whatever &lt;a href=&quot;https://github.com/garrytan/gstack&quot;&gt;gstack&lt;/a&gt;’s &lt;a href=&quot;https://github.com/garrytan/gstack/tree/main/cso&quot;&gt;&lt;code&gt;/cso&lt;/code&gt;&lt;/a&gt;, Cloudflare’s &lt;a href=&quot;https://github.com/cloudflare/security-audit-skill&quot;&gt;&lt;code&gt;security-audit&lt;/code&gt;&lt;/a&gt; skill, &lt;a href=&quot;https://github.com/zubair-trabzada/geo-seo-claude&quot;&gt;&lt;code&gt;/geo-audit&lt;/code&gt;&lt;/a&gt;, or a cost or dead code review turned up, numbered and marked open, PR’d, merged, or rejected. The next run picks from them without redoing the audit. Like, “the last &lt;code&gt;/cso&lt;/code&gt; run found all this stuff and we haven’t fixed the P1s. Let’s fix those.”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expensive audits, only when the backlog is thin.&lt;/strong&gt; Security (alternating &lt;code&gt;/cso&lt;/code&gt; and &lt;code&gt;security-audit&lt;/code&gt;), &lt;code&gt;/geo-audit&lt;/code&gt; for SEO and AI search, a cost review, or a dead code review. Whichever is most overdue.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then it picks one thing. This is the part that keeps it small:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The bar is &lt;strong&gt;an obvious yes from me&lt;/strong&gt;: a PR I’d merge after a quick read. Either the work is genuinely small, or it’s a small next chunk that is complete and useful on its own. Never part 1 of 20: no scaffolding, half-built features, or setup that only pays off in later PRs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It has to be real (evidence it matters), valuable, reviewable in minutes, and provable. And if nothing clears the bar? &lt;em&gt;“Skip the repo and explain why. No PR beats a weak PR.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The cool thing is I’ve watched it break bigger tasks into smaller ones on its own. It doesn’t want to make a big diff, so it figures out the first piece it can get to production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Subtracting, not just adding.&lt;/strong&gt; If you run all of this and never delete code, you end up with a lot of code. The best code is no code. So every pass includes a quick dead code scan, and the prompt tells it to keep a mix. If a repo’s recent Nunebot PRs have all been additions, a solid removal wins a close call. Unused methods, routes, CSS, JS, gems, packages. Doesn’t matter. 46 of the 250 merged PRs deleted more than they added.&lt;/p&gt;
&lt;p&gt;It doesn’t always get it right. It opened a 2,248 line Fireside PR removing marketing controllers and views I’d unrouted in a cutover. I closed it: “Not ready to do this. It was automated by nunebot. I think we leave for when we resurrect them.”&lt;/p&gt;
&lt;h2 id=&quot;taste&quot;&gt;Taste&lt;/h2&gt;
&lt;p&gt;That brings me to taste. Every time I close a PR, ask a question, or say no, it goes into &lt;code&gt;nunebot/taste.md&lt;/code&gt;. After that Fireside PR, it wrote itself a rule called “Unrouted isn’t always dead”: code I recently unrouted on purpose is parked, not dead.&lt;/p&gt;
&lt;p&gt;At one point an agent suggested I rename Flare, since there’s already another Flare out there. Nope. Now there’s a rule: “I’m not renaming Flare, I love the name.”&lt;/p&gt;
&lt;p&gt;My favorite one came from a Flipper Cloud security fix. Before merging, I asked what it would do to performance and whether it was mostly additive, meaning it checks more things but won’t break anything existing and legit. Now every PR has a &lt;strong&gt;Performance &amp;amp; compatibility&lt;/strong&gt; section that answers both before I ask.&lt;/p&gt;
&lt;p&gt;There are 107 of these rules after 11 days. I’ll need to compact them into the prompt at some point. Live and learn.&lt;/p&gt;
&lt;h2 id=&quot;step-three-build-it-shave-it-try-to-break-it&quot;&gt;Step Three: Build It, Shave It, Try to Break It&lt;/h2&gt;
&lt;p&gt;This is my current build, and it’s the part I’m most excited about:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Build.&lt;/strong&gt; Opus 5.5 writes the change and a test that fails on main, in its own worktree with its own ports and databases. Commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/simplify&lt;/code&gt;.&lt;/strong&gt; It’s built into Claude Code, and it usually shaves off a good amount of the crappy AI code. Commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/qa&lt;/code&gt;.&lt;/strong&gt; This one’s from &lt;a href=&quot;https://github.com/garrytan/gstack&quot;&gt;gstack&lt;/a&gt;. It actually uses the thing. If it’s a CLI, it runs the CLI. If it’s an API, it curls it. If it’s in a browser, it clicks through it. After this, I know the code works. Commit the fixes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/codex review&lt;/code&gt;.&lt;/strong&gt; Also gstack. Codex does an adversarial review and fixes anything legit. Then, and this is the trick, it runs &lt;code&gt;/codex review&lt;/code&gt; again on &lt;strong&gt;just the fixes&lt;/strong&gt;, not the whole change. At most three rounds. If Codex is still finding stuff after that, it gives up on that repo, leaves the branch pushed, writes the findings in brain, and moves on.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Reviewing just the fixes is the biggest thing I figured out in October. Before, I’d run a full review, fix, full review, fix, chasing zero findings. Claude especially would go 20 loops finding nits on repeat. That’s how I got the 3,000 line stomach-ache PRs. Now: the code is written, it’s been shaved down, we know it works, and we’ve looked for holes. Ship it.&lt;/p&gt;
&lt;p&gt;Here’s one. On &lt;a href=&quot;https://github.com/jnunemaker/workspace/pull/21&quot;&gt;workspace#21&lt;/a&gt;, round one caught a test stub using Ruby 3 syntax that macOS’s system Ruby 2.6 can’t parse. Fixed. Round two, on just the fix: clean.&lt;/p&gt;
&lt;p&gt;One gotcha. gstack is invasive. Mention deploying and it’ll decide to run its whole &lt;a href=&quot;https://github.com/garrytan/gstack/tree/main/ship&quot;&gt;&lt;code&gt;/ship&lt;/code&gt;&lt;/a&gt; workflow, rerun all your tests, and redo a bunch of work you already did. It’s built for people who aren’t paying attention, and here, the prompt already is. So my global agent instructions say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not automatically invoke, load, or follow any skill. Only use a skill when I explicitly name it in the current request. The presence of an applicable installed skill is not permission to use it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nunebot names each skill explicitly, in order. And since nobody’s watching the run, the prompt says that when a skill stops to ask a question, take the recommended option and note it in the PR.&lt;/p&gt;
&lt;h2 id=&quot;step-four-prove-it&quot;&gt;Step Four: Prove It&lt;/h2&gt;
&lt;p&gt;I never sat down and designed a PR template. I told it what I care about from a verification standpoint, and it built the PR body so I gain confidence as I read down. Why. What changed. Proof. Performance and compatibility. Review. Risk and rollback.&lt;/p&gt;
&lt;figure&gt;
&lt;a href=&quot;https://github.com/flippercloud/flipper/pull/1075&quot;&gt;&lt;img src=&quot;https://www.johnnunemaker.com/images/2026/10/nunebot-pr-proof.png&quot; width=&quot;1068&quot; height=&quot;1236&quot; alt=&quot;The top of a merged Nunebot PR on Flipper, labeled nunebot. It reads Why, then What changed, then Proof: the new spec fails on main and passes on the branch, the whole UI suite fails 71 times under LC_ALL=C on main and passes on the branch, and a gem built from the branch renders where released 1.4.2 raises.&quot;&gt;&lt;/a&gt;
&lt;figcaption&gt;The top of &lt;a href=&quot;https://github.com/flippercloud/flipper/pull/1075&quot;&gt;flipper#1075&lt;/a&gt;. Why, what changed, then proof.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Proof is the part I care about. &lt;a href=&quot;https://thorstenball.com&quot;&gt;Thorsten Ball&lt;/a&gt; from &lt;a href=&quot;https://ampcode.com&quot;&gt;Amp&lt;/a&gt; has been talking about asking for irrefutable proof, and it’s so good. Tell Claude to give you irrefutable proof and it will. If it can’t, it tells you why and gives you everything up to that point. (I’ve talked about &lt;a href=&quot;https://www.johnnunemaker.com/yagni-camping-chair-development/&quot;&gt;prove it&lt;/a&gt; &lt;a href=&quot;https://www.johnnunemaker.com/the-friday-deploy/&quot;&gt;before&lt;/a&gt;. I’m still on it.) Every Nunebot PR needs whichever of these apply:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A test that fails on main and passes on the branch, with both outputs.&lt;/li&gt;
&lt;li&gt;The full suite and linters green.&lt;/li&gt;
&lt;li&gt;The change seen running: the app, a console session, a script.&lt;/li&gt;
&lt;li&gt;Before and after screenshots when a person would see the change.&lt;/li&gt;
&lt;li&gt;Before and after numbers for anything that claims to be faster or cheaper.&lt;/li&gt;
&lt;li&gt;For removals, the searches and signals proving nothing uses the code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s a real one. On South Bend Watch Co, any request that blew up returned a bare 500 in production, even when Rails wanted a friendly 422. The root cause was an old Honeybadger patching a Rails method with two arguments after Rails 7.1 started passing three. The fix was a gem bump plus one test. The proof:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Request                                    main   branch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;POST /users/sign_in with no token           500      422&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;POST /users/sign_in, wrong password         200      200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET /, /watches, /about                     200      200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET /no-such-page                           404      404&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I didn’t ask for that table. I just said I want proof.&lt;/p&gt;
&lt;p&gt;Or this one on PreachIndex. Thousands of passage pages all shared the same generic meta description. Now Romans 8:28’s says it’s cited specifically in 26 sermons and within a wider passage in 89. One new helper, one test, 27 lines. Everything’s tested. You can see it works. Why not merge it?&lt;/p&gt;
&lt;p&gt;A few public ones if you want to see a whole PR: &lt;a href=&quot;https://github.com/flippercloud/flipper/pull/1077&quot;&gt;flipper#1077&lt;/a&gt; (quiets bogus warnings that hit your logs right when a Cloud sync fails), &lt;a href=&quot;https://github.com/jnunemaker/workspace/pull/20&quot;&gt;workspace#20&lt;/a&gt; (a pure deletion, with proof nothing uses it), and &lt;a href=&quot;https://github.com/jnunemaker/httparty/pull/869&quot;&gt;httparty#869&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A few other things it does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Steps for me go at the top.&lt;/strong&gt; If a PR needs something from me, it gets a &lt;code&gt;needs-john&lt;/code&gt; label and a checklist up top. When it added a unique index on lowercased emails in Fireside, the first step was to check production for duplicates. I know that. I also could have easily missed it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rollback.&lt;/strong&gt; If anything is risky, it tells me how to roll back. Really helpful when you aren’t reading the code and something goes sideways.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screenshots, attached.&lt;/strong&gt; At first it wanted to commit screenshots to a branch. I said I’m pretty sure GitHub has an attach method now. “Oh yes, they do.” Now they’re just in the PR. Most of the time when it says it can’t do something, I say “false,” and it finds a way. It always does.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I got about 20 PRs in and stopped reading the code. &lt;strong&gt;I started merging after reading the body.&lt;/strong&gt; Whoa! I like woke up and realized what I was doing. It was a new level of trust. I’ve always read the models and queries, even when I skipped the views. But the proof is that good, and I’ve built this way all October.&lt;/p&gt;
&lt;h2 id=&quot;step-five-let-it-rip&quot;&gt;Step Five: Let It Rip&lt;/h2&gt;
&lt;p&gt;The very first prompt was literally “read this prompt and run it for Flipper Cloud.” It made a PR. Yep, merge. Do it for Box Out. Merge. Do it for Fireside. Merge. Do all three. Merge, merge, merge. Do every repo I’ve committed to in the last year. It’s now opened PRs in 20 repos.&lt;/p&gt;
&lt;p&gt;At first it went one repo at a time, so I asked how it would speed things up. Run things in parallel, obviously. But two worktrees of the same heavy app at once isn’t great, and the Box Out suite alone uses about half my CPUs. So it suggested lanes. A heavy lane for the big Rails apps and a light lane for gems and small apps, running side by side. Now it’s nine lanes, three heavy and six light. Each lane gets its own ports, every database gets a worktree suffix, and even the gstack browser gets its own daemon.&lt;/p&gt;
&lt;p&gt;I also didn’t want a bajillion PRs sitting open and overwhelming me. So I asked what Dependabot does. It caps open PRs at five. Cool, let’s do five. If a repo already has five Nunebot PRs open, that run skips it.&lt;/p&gt;
&lt;p&gt;And the prompt itself? I never wrote it. I opened &lt;a href=&quot;https://wisprflow.ai&quot;&gt;Wispr Flow&lt;/a&gt; and brain dumped. Here’s how I’m building software right now. Here’s where I keep my priorities. Here’s some other random stuff. Then I gave that to Claude and said turn this into a prompt I can rerun from a scheduled task. I ran it on one repo at a time and gave it feedback each time until it was putting out stuff I liked. &lt;strong&gt;Then I let it rip.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I hadn’t even read the whole thing until I walked &lt;a href=&quot;https://ryanwood.com&quot;&gt;a friend&lt;/a&gt; through it this week. I don’t care. I care about the result. Since it lives in brain and gets reread every run, any session I’m in can “add this to the Nunebot prompt,” and it gets a little stronger. It’s 229 lines now, and it’s changed in 19 commits since October 18th.&lt;/p&gt;
&lt;p&gt;About day two, Garrett called me. He’s on every repo I’m on. “What are you doing? I need to understand what you’re doing.” He said at first he figured these couldn’t be OK. Then he looked at them, and they were awesome. So I gave him &lt;a href=&quot;https://gist.github.com/jnunemaker/e421523d2508716450d41a5d785e48ce&quot;&gt;the prompt&lt;/a&gt;, and he had his agent compare it to his own skills to see what he was missing.&lt;/p&gt;
&lt;h2 id=&quot;guardrails&quot;&gt;Guardrails&lt;/h2&gt;
&lt;p&gt;I started it locked down and I’ve given it more as it earned trust, just like a person. The rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read-only on production and every third-party service. Most of that is read-only access. The Linode and Heroku CLIs can make changes, so there it’s the prompt telling it not to.&lt;/li&gt;
&lt;li&gt;No AWS CLI and no AWS credentials.&lt;/li&gt;
&lt;li&gt;Never probe or exploit anything in production. Prove security fixes with tests and local runs.&lt;/li&gt;
&lt;li&gt;Never message customers, reply to tickets, or close issues.&lt;/li&gt;
&lt;li&gt;Never merge, deploy, or flip feature flags.&lt;/li&gt;
&lt;li&gt;Infrastructure cost cuts are recommendations in brain. It never applies them.&lt;/li&gt;
&lt;li&gt;In public repos like HTTParty, never describe a vulnerability in a PR, commit, or branch name. Brain only, and I handle it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-numbers&quot;&gt;The Numbers&lt;/h2&gt;
&lt;p&gt;Here’s every PR labeled &lt;code&gt;nunebot&lt;/code&gt; from the first one on October 18th through today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;269 opened&lt;/strong&gt; across 20 repos. &lt;strong&gt;250 merged&lt;/strong&gt;, 16 open, and 3 closed without merging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+14,328 / −6,107 lines&lt;/strong&gt; across the merged PRs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The median merged PR changed 43 lines.&lt;/strong&gt; 190 of the 250 were under 100 lines, 234 were under 200, and only 3 went over 500. The biggest was an npm security bump on this site, and it’s 2,360 lines of &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2.7 hours&lt;/strong&gt; median from opened to merged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Biggest day: Sunday, October 25th&lt;/strong&gt; (sitting in a camping chair). 63 opened, 63 merged.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now my GitHub contributions graph. For 16 years, 2009 through 2024, I averaged about 1,650 contributions a year. That’s 4.5 a day.&lt;/p&gt;
&lt;p&gt;On December 14th, 2025 I turned on a 20x Claude plan to try Opus 4.5. December 14th through March, I averaged 18 a day. That’s 4x. April through July, I dropped back to 5 a day. April and May were prep for a two month summer camping trip, and June and July we were on the road from Indiana to Oregon, Washington, and Montana. Basically, when my kids are out of school, my throughput drops, and I’m ok with that. August and September were back to 18 a day. The first half of October was 31 a day. Since Nunebot started, it’s been 117 a day.&lt;/p&gt;
&lt;figure&gt;
&lt;svg viewBox=&quot;0 0 640 240&quot; width=&quot;100%&quot; role=&quot;img&quot; aria-labelledby=&quot;contrib-title contrib-desc&quot; style=&quot;font-family:inherit;display:block&quot;&gt;&lt;title id=&quot;contrib-title&quot;&gt;GitHub contributions per day&lt;/title&gt;&lt;desc id=&quot;contrib-desc&quot;&gt;Average contributions a day: 4.5 for 2009 to 2024, 18.2 from Dec 14 2025 to March 2026, 5.1 from April to July 2026, 18.4 for August and September, 31.2 for October 1 to 17, and 117.3 from October 18 to 29 with Nunebot.&lt;/desc&gt;&lt;line x1=&quot;190&quot; y1=&quot;0&quot; x2=&quot;190&quot; y2=&quot;240&quot; style=&quot;stroke:var(--hairline)&quot; stroke-width=&quot;1&quot;/&gt;&lt;g&gt;&lt;title&gt;2009–2024: 4.5 a day&lt;/title&gt;&lt;text x=&quot;178&quot; y=&quot;25&quot; text-anchor=&quot;end&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text-secondary)&quot;&gt;2009–2024&lt;/text&gt;&lt;path d=&quot;M190 8h10.6a4 4 0 0 1 4 4v16a4 4 0 0 1 -4 4h-10.6z&quot; style=&quot;fill:var(--text-secondary)&quot;/&gt;&lt;text x=&quot;212.6&quot; y=&quot;25&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text)&quot;&gt;4.5&lt;/text&gt;&lt;/g&gt;&lt;g&gt;&lt;title&gt;Dec 14 ’25 – Mar ’26: 18.2 a day&lt;/title&gt;&lt;text x=&quot;178&quot; y=&quot;65&quot; text-anchor=&quot;end&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text-secondary)&quot;&gt;Dec 14 ’25 – Mar ’26&lt;/text&gt;&lt;path d=&quot;M190 48h55.0a4 4 0 0 1 4 4v16a4 4 0 0 1 -4 4h-55.0z&quot; style=&quot;fill:var(--text-secondary)&quot;/&gt;&lt;text x=&quot;257.0&quot; y=&quot;65&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text)&quot;&gt;18.2&lt;/text&gt;&lt;/g&gt;&lt;g&gt;&lt;title&gt;Apr – Jul ’26: 5.1 a day&lt;/title&gt;&lt;text x=&quot;178&quot; y=&quot;105&quot; text-anchor=&quot;end&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text-secondary)&quot;&gt;Apr – Jul ’26&lt;/text&gt;&lt;path d=&quot;M190 88h12.5a4 4 0 0 1 4 4v16a4 4 0 0 1 -4 4h-12.5z&quot; style=&quot;fill:var(--text-secondary)&quot;/&gt;&lt;text x=&quot;214.5&quot; y=&quot;105&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text)&quot;&gt;5.1&lt;/text&gt;&lt;/g&gt;&lt;g&gt;&lt;title&gt;Aug – Sep ’26: 18.4 a day&lt;/title&gt;&lt;text x=&quot;178&quot; y=&quot;145&quot; text-anchor=&quot;end&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text-secondary)&quot;&gt;Aug – Sep ’26&lt;/text&gt;&lt;path d=&quot;M190 128h55.6a4 4 0 0 1 4 4v16a4 4 0 0 1 -4 4h-55.6z&quot; style=&quot;fill:var(--text-secondary)&quot;/&gt;&lt;text x=&quot;257.6&quot; y=&quot;145&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text)&quot;&gt;18.4&lt;/text&gt;&lt;/g&gt;&lt;g&gt;&lt;title&gt;Oct 1–17: 31.2 a day&lt;/title&gt;&lt;text x=&quot;178&quot; y=&quot;185&quot; text-anchor=&quot;end&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text-secondary)&quot;&gt;Oct 1–17&lt;/text&gt;&lt;path d=&quot;M190 168h97.1a4 4 0 0 1 4 4v16a4 4 0 0 1 -4 4h-97.1z&quot; style=&quot;fill:var(--text-secondary)&quot;/&gt;&lt;text x=&quot;299.1&quot; y=&quot;185&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text)&quot;&gt;31.2&lt;/text&gt;&lt;/g&gt;&lt;g&gt;&lt;title&gt;Oct 18–29 (Nunebot): 117.3 a day&lt;/title&gt;&lt;text x=&quot;178&quot; y=&quot;225&quot; text-anchor=&quot;end&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text-secondary)&quot; font-weight=&quot;600&quot;&gt;Oct 18–29 (Nunebot)&lt;/text&gt;&lt;path d=&quot;M190 208h376.0a4 4 0 0 1 4 4v16a4 4 0 0 1 -4 4h-376.0z&quot; style=&quot;fill:var(--text)&quot;/&gt;&lt;text x=&quot;578.0&quot; y=&quot;225&quot; font-size=&quot;14&quot; style=&quot;fill:var(--text)&quot; font-weight=&quot;600&quot;&gt;117.3&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;figcaption&gt;Average GitHub contributions per day.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;October 19th through 25th, I had 1,113 contributions in seven days. That’s more than I had in all of 2018 or 2019, and about two-thirds of an average year. &lt;strong&gt;That Sunday hit 287 in one day. Before this year, my biggest day ever was 70.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Contributions are a goofy metric. Nunebot commits after every step, so every PR counts several times. &lt;strong&gt;Don’t read this as 25x the value. Read it as: the chores are getting done, and I’m not the one doing them.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;Right now Nunebot picks whatever it thinks is best. Where I want to go is three stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Find work.&lt;/strong&gt; Security audits, GEO and marketing audits, errors, and performance checks run on a regular basis and just add tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prioritize.&lt;/strong&gt; Rank the tasks by impact: the odds we get hacked, revenue, or a customer having a problem we’d like them to not have.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build and prove.&lt;/strong&gt; Everything you just read.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’d say yes, yes, yes, yes to the work at stage two. And once I’ve said yes, I honestly don’t think I need to see the PR. I know that’s a weird thing to say, but the process has been pumping out such good code that my confidence is way up.&lt;/p&gt;
&lt;p&gt;I also need a Nunebot GitHub app. Right now it asks questions as me, and then I answer them, also as me. It’s really funny.&lt;/p&gt;
&lt;p&gt;The question I ask about every run is simple: &lt;strong&gt;did I get a small chunk of code that made my app better, without me being involved?&lt;/strong&gt; That’s all I want.&lt;/p&gt;
&lt;p&gt;And lately the answer is yes about 250 times over.&lt;/p&gt;
</content:encoded><category>ai</category><category>programming</category></item><item><title>The Friday Deploy: Changing the Wheels While the Car Is Moving</title><link>https://www.johnnunemaker.com/the-friday-deploy/</link><guid isPermaLink="true">https://www.johnnunemaker.com/the-friday-deploy/</guid><description>Garrett and I started a new podcast about shipping software with confidence. In episode one, we talk about rebuilding Fireside while it was live, including Postgres 9.5 to 18 in one hop.</description><pubDate>Thu, 01 Oct 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Garrett and I started a podcast. It’s called &lt;a href=&quot;https://thefridaydeploy.fireside.fm&quot;&gt;The Friday Deploy&lt;/a&gt;, and the tagline is the whole point: ship on Friday, sleep through the night.&lt;/p&gt;
&lt;p&gt;We already do a marketing and business podcast for Fireside, so we figured, let’s go full on dev with this one. Feature flags, deployments, infrastructure, incidents, product decisions, and all the little practices that let you deploy whenever you want without the sweaty armpits. Sometimes it’ll be just me and Garrett, and sometimes we’ll bring on a guest to walk through what they do.&lt;/p&gt;
&lt;p&gt;Subscribe on &lt;a href=&quot;https://podcasts.apple.com/us/podcast/the-friday-deploy/id6816197157&quot;&gt;Apple Podcasts&lt;/a&gt;, &lt;a href=&quot;https://open.spotify.com/show/4tplJ47HVq7Yn4o07zjQzI&quot;&gt;Spotify&lt;/a&gt;, or &lt;a href=&quot;https://feeds.fireside.fm/thefridaydeploy/rss&quot;&gt;RSS&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;episode-1-changing-the-wheels-while-the-car-is-moving&quot;&gt;Episode 1: Changing the Wheels While the Car Is Moving&lt;/h2&gt;
&lt;iframe src=&quot;https://player.fireside.fm/v2/wKHjBfwC+s5MvqGP7&quot; title=&quot;Episode 1: Changing the Wheels While the Car Is Moving&quot; loading=&quot;lazy&quot; style=&quot;aspect-ratio: auto; height: 130px; color-scheme: light;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;We’re almost exactly two years into &lt;a href=&quot;https://www.johnnunemaker.com/acquiring-fireside/&quot;&gt;owning Fireside&lt;/a&gt;, so for the first episode we looked back at what we inherited and how we rebuilt nearly all of it without taking it down. Remember that meme of a car driving on two wheels while people hang out the side changing the other two? That’s basically been the last two years.&lt;/p&gt;
&lt;p&gt;Some of the highlights:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What We Inherited.&lt;/strong&gt; Fireside was eight years old when we bought it. Ruby 2.7, Rails 5, Postgres 9.5, and servers on a version of Ubuntu that had been dead for years. No Puppet, no Ansible, nothing that said “here’s how you recreate this.” Today almost none of that exists anymore. Garrett walked the app up through Ruby and Rails, and I swapped out every single server. The last of the old workers got retired the day before we recorded. (We talked about the early days of that cleanup on the &lt;a href=&quot;https://www.johnnunemaker.com/ruby-on-rails-podcast-taking-over-fireside-fm/&quot;&gt;Ruby on Rails Podcast&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Self-Documenting Infrastructure.&lt;/strong&gt; The process I keep coming back to: have the agent reverse engineer everything a server does into a markdown file, then tell it to recreate that on a brand new box with no downtime. Same thing for moving off Dan’s Cloudflare account. Document every setting that differs from the defaults, then rebuild it. I punted on that Cloudflare move for a year and eight months. With AI it was done in a sitting. I told the camper version of this story on &lt;a href=&quot;https://www.johnnunemaker.com/yagni-camping-chair-development/&quot;&gt;YAGNI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Postgres 9.5 to 18 in One Hop.&lt;/strong&gt; A sane person would have done this in a few jumps. We went from self-managed 9.5 to managed 18 in one shot. Everyone I asked for help just laughed. I waited months for the models to get better, ran the plan past something like six of them, and &lt;a href=&quot;https://www.johnnunemaker.com/shrinking-a-postgres-table/&quot;&gt;shrank a bloated table&lt;/a&gt; so 80 GB became about 20 GB and the copy only took ten minutes. Reads stayed up the whole time. Feeds and downloads kept working. Podcasters love their downloads, so that part was non-negotiable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flipper as the Big Red Button.&lt;/strong&gt; The go-live moment was flipping a database read-only flag. The whole app switched to read-only mode and showed a friendly message in the studio. I shut off the workers, did the migration, flipped the flag back, and let the workers chew through the queued downloads. Zero support emails. The scariest part was Codex deciding to compact for five minutes right before the switchover.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Taste Is Still the Job.&lt;/strong&gt; Garrett runs every plan past multiple models. Claude reviews Codex, Codex reviews Claude, Copilot reviews the PR. Fresh eyes without the session baggage catch things. But ask enough agents and you get a pile of opinions, and they will all happily over-engineer anything. Garrett’s agent once tried to add a feature flag to clean up a helper. Knowing what to say no to is &lt;a href=&quot;https://www.johnnunemaker.com/hallway-track-ai-taste/&quot;&gt;taste&lt;/a&gt;, and it’s what separates hacking around from shipping software you won’t regret.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prove It.&lt;/strong&gt; When we each asked AI to review months of our transcripts and suggest missing skills, the top one for both of us was some version of “prove it.” My biggest tip right now: when you feel anxious and keep adding more prompts to harden something, your problem isn’t the prompt. It’s that you haven’t figured out how to verify it. When I moved to Caddy, the agent tested the new certificates with curl and custom host headers before switching anything. I wouldn’t have even known I could test that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read-Only Production Context.&lt;/strong&gt; All our projects now have a read-only psql script the agent can use against a replica. When it starts overbuilding, I tell it to check our actual scale and then decide. Garrett does the same for UI work. How many customers have 20 podcasts? Not enough to need search in the podcast dropdown. Real data beats guessing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep Deterministic Work Deterministic.&lt;/strong&gt; Garrett rebuilt &lt;a href=&quot;https://github.com/garrettdimon/reviewer&quot;&gt;Reviewer&lt;/a&gt; so agents can discover and run linters, tests, and security checks without burning tokens or freelancing CLI flags. Fewer degrees of freedom, more reliable outcomes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scheduled Tasks Are My Beating Heart.&lt;/strong&gt; Every morning I get a &lt;a href=&quot;https://flare.am&quot;&gt;Flare&lt;/a&gt; rundown across all our apps: traffic up or down, performance regressions, and what probably caused them. That’s how I found out a crawler was slurping half a million requests a day from one app. Then I wanted it for open source, then Stripe, then QuickBooks. Cron plus AI plus access to context is extremely powerful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do We Even Need an Interface?&lt;/strong&gt; I haven’t opened the Flare web UI since the MCP started working. I just ask Codex what’s slow and it tells me. I floated &lt;a href=&quot;https://www.johnnunemaker.com/code-with-jason-people-buy-from-people/&quot;&gt;ops without a UI&lt;/a&gt; on Code with Jason, and now I’m seriously considering shipping Flare with no UI except a place to put your credit card. An alert is just a scheduled task. Garrett pushed it further: maybe the interface is assembled on the fly from a link, showing exactly the evidence for the thing that’s wrong and nothing else.&lt;/p&gt;
&lt;p&gt;We also got into letting agents carry work all the way to the final approval (I started a worker swap, forgot about it, and got a push notification hours later asking me to approve the cutover), optimistic versus pessimistic locking, and why a feature flag is not a million dollar bank transfer.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://thefridaydeploy.fireside.fm/1&quot;&gt;Listen to episode one&lt;/a&gt;. If you’re the kind of person who wants to deploy on Friday and actually enjoy your weekend, &lt;a href=&quot;https://thefridaydeploy.fireside.fm/subscribe&quot;&gt;subscribe&lt;/a&gt;. We’re going to try and do this regularly.&lt;/p&gt;
</content:encoded><category>podcast</category><category>fireside</category><category>flipper</category><category>ai</category></item><item><title>YAGNI: Camping Chair Development</title><link>https://www.johnnunemaker.com/yagni-camping-chair-development/</link><guid isPermaLink="true">https://www.johnnunemaker.com/yagni-camping-chair-development/</guid><description>I joined Matt Swanson on YAGNI to talk about why I&apos;m not a craftsman anymore, rewriting Fireside&apos;s infrastructure from a camper, too many open agent loops, and why verification is now the hard part.</description><pubDate>Thu, 01 Oct 2026 16:00:00 GMT</pubDate><content:encoded>&lt;iframe src=&quot;https://share.transistor.fm/e/77f3a7b0&quot; title=&quot;YAGNI: Camping chair development w/ John Nunemaker&quot; loading=&quot;lazy&quot; scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;I joined &lt;a href=&quot;https://x.com/_swanson&quot;&gt;Matt Swanson&lt;/a&gt; on &lt;a href=&quot;https://yagni.fm/episodes/camping-chair-development-w-john-nunemaker&quot;&gt;YAGNI&lt;/a&gt; to talk about how I build software now across &lt;a href=&quot;https://fireside.fm&quot;&gt;Fireside&lt;/a&gt;, &lt;a href=&quot;https://www.flippercloud.io&quot;&gt;Flipper&lt;/a&gt;, and &lt;a href=&quot;https://boxoutsports.com&quot;&gt;Box Out&lt;/a&gt;. Some of it even happens from a camping chair.&lt;/p&gt;
&lt;p&gt;Some of the highlights:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m Not a Craftsman.&lt;/strong&gt; I always thought of myself as a craftsman. Bespoke furniture. Samurai. Honor. All that. Then on December 14th I turned on a 20x Claude plan just to try Opus and realized nope, I just love shipping stuff and solving problems. These days I read the data models and the queries because those affect how fast things are for customers. JavaScript and view code? I don’t read it. Does it look right? Is it responsive? What’s my Lighthouse score? Good enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stripping Back.&lt;/strong&gt; I went through a phase of plugins. Compound engineering, gstack, etc. Lately I’m removing things. The tools love to be perfectly correct and solve every theoretical race condition, and you end up with a ball of mutexes that gives me a stomach ache. Is it theoretically wrong? Yes. Does it ever happen in practice? I don’t think so. Less perfectionism, more shipping.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rewriting Fireside’s Infrastructure From a Camper.&lt;/strong&gt; When we &lt;a href=&quot;https://www.johnnunemaker.com/acquiring-fireside/&quot;&gt;acquired Fireside&lt;/a&gt;, it was on super outdated Ubuntu and Postgres 9.5 with SSL handled by a tangle of Nginx, background jobs, and Redis. I asked what tool does all this and the answer was Caddy. Reverse engineer the current system into a markdown file, describe the new state, let it rip. I missed one host header. In three days I rewrote about 80% of our infrastructure while driving a camper six hours a day and sitting by a fire at night. I’m a scaredy cat about downtime, but it comes up with ways to smoke test everything before you flip the switch that I would never have done before.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Brain or Hands.&lt;/strong&gt; Matt had a great framing: sometimes you’re the brain and the agent is the hands, and sometimes it flips. I hate being the hands, clicking through some cloud console because the agent told me to. So now I make a temporary API key that expires in an hour and let it do the work. Honestly, I want a new kind of key: read only, plus write access to anything it creates, for a limited time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Too Many Open Loops.&lt;/strong&gt; I want a hundred agents cranking on stuff for me all the time. But every agent tool shows you all of them, all the time, and each one sticks in the back of your brain. What I want is something like Railway’s canvas for agents. Every &lt;a href=&quot;https://www.johnnunemaker.com/conducting-rails/&quot;&gt;worktree&lt;/a&gt; is a box on an infinite canvas. Group the Flipper gem, Flipper Pro, and Flipper Cloud worktrees together, move the rest out of the way, and stop carrying it all in my head. I’m too lazy to build it. Someone please build it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prove It.&lt;/strong&gt; I had the agent go through my past chats and tell me what to turn into skills. Number one was &lt;code&gt;/prove&lt;/code&gt;: prove to me that what you did works all the way through. Then I ask how we can break it. Or I just gaslight it with “make &lt;a href=&quot;https://www.johnnunemaker.com/fewer-and-faster/&quot;&gt;fewer network calls&lt;/a&gt;” and it goes, “you’re right, I missed these N+1s.” Verification is now the biggest blocker and the hardest thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fake Everything.&lt;/strong&gt; Matt brought up the idea of having agents build fake versions of third party APIs, like a vibe coded fake Slack that runs next to your app. No more booting on port 3000 so OmniAuth callbacks work. Simulate rate limits and outages with a flag. It reminded me of Toxiproxy from my GitHub availability days. Box Out is all social integrations, so we should totally do this for X and Meta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Straight to Prod.&lt;/strong&gt; Garrett asked if we could finally set up staging for Fireside. I don’t use staging anymore. I want to get to prod as fast as I can, because that’s where I feel it now. Matt nailed this: the flow and satisfaction didn’t go away, it just happens at a different point in the process. It used to be when the code was clean. Now it’s when the thing is live and people are using it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Talking All Day.&lt;/strong&gt; I’ve had &lt;a href=&quot;https://www.johnnunemaker.com/pain-and-programming/&quot;&gt;hand and arm problems&lt;/a&gt; my whole programming life. Now I talk to my agents all day with Wispr Flow. My only problem is a sore throat. If anything, AI has prolonged my career.&lt;/p&gt;
&lt;p&gt;We also got into Cloudflare Tunnel for checking work from my phone, proving to an iCal provider that their feeds were broken (zero bytes since the day they launched their new thing), and why feature flags and APMs matter more when nobody reads the code. APMs are going to have to get a lot cheaper.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://yagni.fm/episodes/camping-chair-development-w-john-nunemaker&quot;&gt;Listen to the full episode on YAGNI&lt;/a&gt;. I hope you enjoy it. If you want more, I talked about taste and AI on &lt;a href=&quot;https://www.johnnunemaker.com/hallway-track-ai-taste/&quot;&gt;The Hallway Track&lt;/a&gt; last month.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2026-10-01&lt;/strong&gt;: Garrett and I went deeper on the Fireside rebuild, including Postgres 9.5 to 18 in one hop with no downtime for listeners, in episode one of our new podcast, &lt;a href=&quot;https://www.johnnunemaker.com/the-friday-deploy/&quot;&gt;The Friday Deploy&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>podcast</category><category>ai</category></item><item><title>The Hallway Track: Why AI Rewards People Who Already Have Taste</title><link>https://www.johnnunemaker.com/hallway-track-ai-taste/</link><guid isPermaLink="true">https://www.johnnunemaker.com/hallway-track-ai-taste/</guid><description>I joined The Hallway Track to talk about running multiple companies, how feature flags change the way you build, and why taste matters more in the AI era.</description><pubDate>Wed, 26 Aug 2026 16:00:00 GMT</pubDate><content:encoded>&lt;iframe src=&quot;https://www.youtube.com/embed/Ph4kuAZdHcc?feature=oembed&quot; title=&quot;Why AI Rewards People Who Already Have Taste&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;I joined Chris on &lt;a href=&quot;https://www.youtube.com/watch?v=Ph4kuAZdHcc&quot;&gt;The Hallway Track&lt;/a&gt; to talk about running &lt;a href=&quot;https://boxoutsports.com&quot;&gt;Box Out Sports&lt;/a&gt;, &lt;a href=&quot;https://fireside.fm&quot;&gt;Fireside&lt;/a&gt;, and &lt;a href=&quot;https://www.flippercloud.io&quot;&gt;Flipper&lt;/a&gt; while still maintaining open source projects like &lt;a href=&quot;https://github.com/jnunemaker/httparty&quot;&gt;HTTParty&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We covered a lot of ground in 50 minutes: why I gave up trying to focus, the production problem that led to Flipper, how feature flags change the way teams build software, and why taste might be the real advantage in the AI era.&lt;/p&gt;
&lt;p&gt;Some of the highlights:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Giving Up on Focus.&lt;/strong&gt; I spent years feeling like I was broken because I couldn’t focus on one thing. Everyone tells you focus is how you do something big and great, but I like having a lot of things going. If I get jammed on one project, I can switch to the next most important thing on another instead of dropping to zero. I don’t push one thing forward as far or as fast, but I push a lot of things forward regularly. I finally gave up trying to be someone else about three years ago.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What AI Changed at Box Out.&lt;/strong&gt; The biggest change is amplification. We have shipped so much in the last six months. But we’re also doing things we would have just said no to before. For years Box Out was built for speed, not customization. Recreating Photoshop in the browser was way outside our niche and not worth the time. Now we’re building a Figma or Photoshop-style template editor because, well, what if we did both?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Scaling Crisis That Led to Flipper.&lt;/strong&gt; While working on Words With Friends, we were having scaling problems because a bunch of celebrities started playing and traffic went nuts. I kept rolling out a new caching system and taking down the site because the cache was cold. Then we tried a percentage rollout. I went from 1% to 5% until the cache was warm enough and then let it rip. Feature flags were amazing, but the library we used only supported Redis. I built Flipper so your flags could live in Active Record, environment variables, a file on disk, or whatever made sense for your application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Feature Flags Change How You Build.&lt;/strong&gt; Technically a flag is just a quick check to see if something is on or off. But once people start using them, you have to pry them out of their cold dead fingers. You can get unfinished work into production, let the right people try it, and turn it off without another deploy. Everything gets easier, less stressful, and less scary. Chris shared an old newspaper phrase I loved: “demos over memos.” Just get something on a screen that people can react to. If it’s behind a flag, who cares? Nobody sees it. You didn’t break anything. It’s fine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintaining Open Source With AI.&lt;/strong&gt; I get much better pull requests for HTTParty now. They show up with tests and are often pretty close to ready to ship. AI also helps me research what to work on next and triage security reports. Half the time I read one of those reports and think, bro, I don’t even know what language this is. Now I can understand it, figure out if it’s real, and release CVE fixes faster. That makes a huge difference for a project I maintain in my spare time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Taste Is the Advantage.&lt;/strong&gt; Everyone I see exploding with AI already had taste and ideas. They were good without it. They were just constrained by time, brainstorming, research, and all the other stuff around creating a product. AI amplifies that by a different multiplier. It also amplifies bad taste and lets dysfunctional teams make a mess ten times faster. The building was never the hard part. Figuring out what to build was.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building Games With My Son.&lt;/strong&gt; We started with AI-generated coloring pages and bedtime stories. Then we made Chickens vs. Zombies, where chickens shoot eggs out of their butts to kill zombies. Obviously. From there we built a Minecraft-ish game with 3D worlds, chat, portals, and Cloudflare Durable Objects. Neither of us had ever made a game. Now he can build what’s in his imagination, play it with his friends, and learn the same things I do when I build software: ask questions, prove that it works, and keep making it better. He used to say he wanted to be an inventor. Now he says he’s going to be a programmer.&lt;/p&gt;
&lt;p&gt;We also got into turning open source into a business, the future of runtime controls in Flipper, why AI exposes dysfunction inside engineering teams, and what programming may look like for the next generation.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Ph4kuAZdHcc&quot;&gt;Watch the full conversation on YouTube&lt;/a&gt;. I hope you enjoy it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2026-10-01&lt;/strong&gt;: Garrett and I dug into taste, over-engineering agents, and verification in episode one of our new podcast, &lt;a href=&quot;https://www.johnnunemaker.com/the-friday-deploy/&quot;&gt;The Friday Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2026-10-01&lt;/strong&gt;: I went deeper on how I actually build with AI now on &lt;a href=&quot;https://www.johnnunemaker.com/yagni-camping-chair-development/&quot;&gt;YAGNI&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>podcast</category><category>business</category><category>ai</category></item><item><title>Code with Jason: People Buy From People</title><link>https://www.johnnunemaker.com/code-with-jason-people-buy-from-people/</link><guid isPermaLink="true">https://www.johnnunemaker.com/code-with-jason-people-buy-from-people/</guid><description>I went on Code with Jason for the second time. Jason and I got into the Fireside acquisition, how to actually find and finance a business to buy, why…</description><pubDate>Mon, 01 Jun 2026 18:47:54 GMT</pubDate><content:encoded>&lt;p&gt;I went on &lt;a href=&quot;https://www.codewithjason.com/podcast/19168361-320-john-nunemaker-of-fireside-fm-box-out-sports-and-very-good-software/&quot;&gt;Code with Jason&lt;/a&gt; for the second time. Jason and I got into the Fireside acquisition, how to actually find and finance a business to buy, why support is a product, and where I think developer tools are headed in the AI era.&lt;/p&gt;
&lt;p&gt;Some of the highlights:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why I Bought Fireside.&lt;/strong&gt; Complete happenstance. I was already paying Garrett Dimon to push Flipper forward, and Garrett had worked on Fireside for Dan Benjamin back in the day. Dan had lost interest and was trying to sell, but it was too big for the small players and too small for the big ones. The math was simple: if I bought it, the profit could pay for Garrett to work on both Fireside and Flipper instead of it coming out of my pocket every month. I’m a long-term person, podcasts aren’t slowing down, and it lined up with the analytics and content hosting I’d done before with Gauges and Harmony. So we did it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finding Deals.&lt;/strong&gt; The marketplaces (Quiet Light, FE International, acquisition.com) are mostly where people list a dying SaaS expecting four to eight times multiples. That’s not reality. A declining SaaS is more like 1.5 to 2.25x. The deals that actually work come through your network, someone who knows someone quietly looking to sell. Stick to what you know. And since the paperwork is identical whether the thing makes fifty thousand or five million, you might as well take the bigger swing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Financing It.&lt;/strong&gt; You don’t need a pile of cash. My house was paid off, so I borrowed against the equity. Banks know houses cold, so they’ll lend up to 80% the next day, no questions asked. Farmland is more like 50%. SBA loans were running 10 to 12% at the time versus 5 to 6% for a bank loan, so the margin matters. I took a 15-year term for the low payment even though I plan to pay it off in five to eight. It forces the business to clear at least that much profit, and then it just quietly pays for itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;People Buy From People.&lt;/strong&gt; Right now Fireside’s edge is simple: Garrett and I do the support and build the product. If you send me the right request and I can knock it out fast, I’ll ship it before I even reply. Most customers aren’t technical (lots of Linux shows and religious orgs), and they value a real person who’ll hop on a Zoom and fix their DNS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Marketing to the People You Already Have.&lt;/strong&gt; Churn is around 2% and most of the focus has been telling existing customers we’re listening and shipping. Churn is a trailing indicator. By the time someone leaves, it’s too late to do anything about it. So we get ahead of it. The more aggressive growth (conferences, underserved niches) is the next wave.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Onboarding and Retention Are the Same Coin.&lt;/strong&gt; Frictionless onboarding helps the self-serve people and the hand-held people at the same time. Every time you onboard someone manually, automate a little more of it so the next one is easier. I just rebuilt Flipper’s migration to be one click from the UI, the CLI, or the console. So slippery you slide right down the waterfall, and then you’re pulling out your credit card wondering what just happened.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Scrappy, Opinionated APM.&lt;/strong&gt; I’m building a lightweight APM (I called it Caboose on the show, it’s Flare now). The trick is that being opinionated makes it cheap. The expensive tools store everything so you can ask any question later. I’d rather pre-compute the answers to the questions you actually ask: count, sum, and error count per endpoint and job, the network breakdown (db, view, cache), and a handful of samples of slow requests. That solves 80 to 90% of performance problems for ten to fifty bucks a month instead of New Relic pricing that explodes the moment you add a teammate. This comes straight from my GitHub years doing analytics, statsd dashboards, and the Datadog migration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ops Without a UI.&lt;/strong&gt; Honestly you barely need a dashboard. You want an API, a CLI, and skills or MCP so Claude can speak your language and pull the data. I did this with Flipper: dumped the gate data and 30 days of telemetry, asked Claude which flags I could delete, and cleared out about 15 stale flags and 600 lines of code in one pass. That’s the moment I knew I wanted the same thing for the APM. Knowing what’s happening in production and shipping safely matters more every month as people read less of the code themselves. GitHub Scientist style stuff, running two code paths and comparing them, is next on my radar.&lt;/p&gt;
&lt;p&gt;We also got into Riverside versus Descript, why trying to do recording, editing, and hosting all at once turns into the PDA of old, being friends with your competitors, and my current obsession with Railway.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;bookmark&quot; href=&quot;https://www.codewithjason.com/podcast/19168361-320-john-nunemaker-of-fireside-fm-box-out-sports-and-very-good-software/&quot;&gt;&lt;span class=&quot;bookmark-title&quot;&gt;320 - John Nunemaker of Fireside.fm, Box Out Sports and Very Good Software - The Code with Jason Podcast&lt;/span&gt;&lt;span class=&quot;bookmark-description&quot;&gt;In this episode I talk with John Nunemaker about his journey with Ruby, acquiring Fireside.fm, and his ventures in software focused on podcast hosting and feature flag services. We also explore the potential impacts of AI in the development space and John’s vision for the future.Links:- Box Out Sports- Very Good Software- Flipper Cloud- Fireside.fm- Nonsense Monthly&lt;/span&gt;&lt;span class=&quot;bookmark-domain&quot;&gt;codewithjason.com&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2026-10-01&lt;/strong&gt;: I’ve stopped opening Flare’s web UI entirely and just ask my agent what’s slow. I talked about maybe shipping it with no UI at all on &lt;a href=&quot;https://www.johnnunemaker.com/the-friday-deploy/&quot;&gt;The Friday Deploy&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>podcast</category></item><item><title>IndieRails Podcast: The Conductor</title><link>https://www.johnnunemaker.com/indierails-podcast-the-conductor/</link><guid isPermaLink="true">https://www.johnnunemaker.com/indierails-podcast-the-conductor/</guid><description>I hopped on IndieRails with Jess and Jeremy (again). We covered a lot of ground in about 90 minutes. Quick business catch up and then dove into how AI has…</description><pubDate>Thu, 19 Mar 2026 19:11:23 GMT</pubDate><content:encoded>&lt;p&gt;I hopped on &lt;a href=&quot;https://www.indierails.com/75&quot;&gt;IndieRails with Jess and Jeremy&lt;/a&gt; (&lt;a href=&quot;https://www.johnnunemaker.com/indie-rails/&quot;&gt;again&lt;/a&gt;). We covered a lot of ground in about 90 minutes. Quick business catch up and then dove into how AI has changed pretty much everything about how I work.&lt;/p&gt;
&lt;p&gt;Some of the highlights:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business Updates&lt;/strong&gt;. Flipper Cloud almost doubled revenue last year, with most of the growth in the last few months. No AI headwinds on docs or conversion. Fireside is stabilizing — we flattened churn and had our first three month stretch of even or growing customers since taking it over. Momentum, the coworking space I invested in, is filling up and becoming a real community in South Bend.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Following the Inspiration&lt;/strong&gt;. I’m a mood person. I follow the energy. Having multiple products at different stages (five, six, and seven figures) means there’s always something that matches whatever I’m feeling that day. I’ve spent 20 years intentionally building toward that flexibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support-Driven Development&lt;/strong&gt;. With Fireside, I’m close to the customer again. Someone asks for a responsive player, I build one from scratch in three days. Someone wants playlists, done. Someone wants custom colors saved, done in an hour. They’re blown away because the product had been dormant. That feedback loop is addicting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Company Brain&lt;/strong&gt;. For Box Out, I’m building an internal AI system using LibreChat with MCPs for our read replica database, Stripe, and a custom internal API. The goal is letting our small team query business data naturally — hydrate conference attendee lists, look up support patterns, pull subscriber counts. Eventually I’ll wire in Metabase too for the verified reports.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Working Styles&lt;/strong&gt;. Jeremy, Jess, and I got into how different developers use AI differently. Garrett goes heavy on docs and specs and skills. I go vanilla — whisper flow, let it roll, see what happens. Both approaches work. A lion doesn’t concern himself with tokens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Arbitrage&lt;/strong&gt;. There’s a window right now where you can get significantly more done. Not just a little more — I’d say I’m working 1.3-1.5x the hours but shipping 10x. That won’t last forever, but right now the gap between people using these tools and people who aren’t is massive. And we’re still just the nerds. Most people haven’t even tried a chatbot yet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s Next&lt;/strong&gt;. I think the AI era means more people shipping more code, which means more demand for developer tools — monitoring, debugging, feature flags, experiment frameworks. That’s why I’m building an APM tool and planning to build something like GitHub Scientist next. The incumbents have no incentive for you to send less data. I think there’s room for opinionated, affordable, AI-friendly developer tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conductor in the Web&lt;/strong&gt;. What I really want is my dev environment on a server somewhere, always on, accessible from my phone. Spin up workspaces, prompt the next thing, check in from the couch or car line. Someone’s going to solve this well in the next couple months. I’d pay as much as I do for Claude for that solution.&lt;/p&gt;
&lt;p&gt;We also got into the health and sustainability of all this, the farming lifestyle analogy (Jeremy and I both grew up in farm country), consulting economics in the AI era, and whether developers should be pushing harder toward ownership.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.indierails.com/75&quot;&gt;Listen here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Codex remote access turned out to be most of what I wanted. My already set up Mac sits on my desk and I fire off commands from my phone, even from a camping chair. I talked about it on &lt;a href=&quot;https://www.johnnunemaker.com/yagni-camping-chair-development/&quot;&gt;YAGNI&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>podcast</category></item><item><title>Rails Business Podcast (again)</title><link>https://www.johnnunemaker.com/rails-business-podcast-again/</link><guid isPermaLink="true">https://www.johnnunemaker.com/rails-business-podcast-again/</guid><description>How AI has changed my day-to-day as a developer.</description><pubDate>Thu, 05 Mar 2026 20:14:06 GMT</pubDate><content:encoded>&lt;p&gt;I joined Brendan Buckingham and Ryan Frisch on the &lt;a href=&quot;https://www.railsbusiness.com/2435951/episodes/18785602-john-nunemaker-on-ai-development&quot;&gt;Rails Business Podcast&lt;/a&gt; to talk about how AI has changed my day-to-day as a developer.&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;bookmark&quot; href=&quot;https://www.railsbusiness.com/2435951/episodes/18785602&quot;&gt;&lt;span class=&quot;bookmark-title&quot;&gt;John Nunemaker on AI Development - Rails Business&lt;/span&gt;&lt;span class=&quot;bookmark-description&quot;&gt;John Nunemaker returns to discuss how fast AI tooling has changed and argues tech leaders should adopt Claude Max (defaulting to Opus), set up agent-friendly local environments, and roll that out to their teams. He shares how his company built an…&lt;/span&gt;&lt;span class=&quot;bookmark-domain&quot;&gt;railsbusiness.com&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are the highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Your job as a tech leader is to go first.&lt;/strong&gt; Get Claude Code with Opus, set up your local dev environment to be agent-friendly, then do the same for everyone on your team. I told everyone to get the $100 Max plan and offered to pay for it myself if they didn’t think it was worth it. No one’s taken me up on that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“Brain,” an AI-in-a-box for the company.&lt;/strong&gt; I built an internal tool on a cheap Hetzner server using LibreChat, Postgres MCP (read-only replica), Stripe MCP, and a custom support MCP. Non-technical teammates can now ask natural language questions about customers, support trends, and revenue data without going through me. Secured it all behind Tailscale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The support insights were eye-opening.&lt;/strong&gt; By slurping all our Crisp support conversations into a database and hooking it up as an MCP, I could ask things like “summarize the last week of support requests” and “what’s the tone of our support person?” and get real answers. Next step is cron jobs that automatically post weekly summaries to Slack.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude as a code review spin cycle.&lt;/strong&gt; Team members now run Claude review locally, fix issues, then run &lt;code&gt;@claude&lt;/code&gt; review on the GitHub PR before requesting human review. By the time it gets to me, it’s usually ready to merge. This solved the bottleneck of everyone suddenly producing several PRs a day.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.conductor.build/&quot;&gt;&lt;strong&gt;Conductor&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;changed the game for parallel work.&lt;/strong&gt; It manages isolated worktrees with separate databases, ports, and sessions. I can work on 3-5 things simultaneously, like a feature, a bug from Honeybadger, a support request, each in its own workspace. Setup and teardown scripts handle the isolation (fewer than 100 lines of code per app).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People who couldn’t write Rails before now can.&lt;/strong&gt; &lt;a href=&quot;https://zastrow.co/&quot;&gt;Phillip&lt;/a&gt;, who we brought on for React/HTML, is now consistently making solid Rails PRs. Claude bridges the gap. The first PR needed some guidance on resiliency patterns, but after that, the spin cycle workflow handles it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caddy for local HTTPS and custom domains.&lt;/strong&gt; Drop it in your Procfile, point wildcard subdomains at it, and you get HTTPS locally with custom domain support. Solved a bunch of pain around cookies, CORS, and multi-workspace development.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stay bullish.&lt;/strong&gt; Software engineering jobs are reportedly up about 4.5% month over month. There’s an arbitrage window right now for teams that adopt these tools early. Don’t get behind. That’s the worst place to be.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>podcast</category></item><item><title>Conducting Rails</title><link>https://www.johnnunemaker.com/conducting-rails/</link><guid isPermaLink="true">https://www.johnnunemaker.com/conducting-rails/</guid><description>A practical guide to running Ruby on Rails across parallel Conductor workspaces, including ports, databases, setup scripts, CORS, and cleanup.</description><pubDate>Fri, 30 Jan 2026 18:01:29 GMT</pubDate><content:encoded>&lt;h2 id=&quot;in-short&quot;&gt;In short&lt;/h2&gt;
&lt;p&gt;Conductor creates isolated workspaces for running multiple coding-agent sessions in parallel. To make that model reliable with Rails:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy required gitignored files into each workspace.&lt;/li&gt;
&lt;li&gt;Derive ports, hosts, cookies, and generated URLs from &lt;code&gt;CONDUCTOR_PORT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Give every workspace its own development and test databases.&lt;/li&gt;
&lt;li&gt;Automate setup, startup, and teardown scripts.&lt;/li&gt;
&lt;li&gt;Account for shared services such as S3 and make each browser tab identifiable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’ve been using &lt;strong&gt;@conductor_build&lt;/strong&gt; heavily and it’s completely changed how I ship Ruby on Rails code when working with Claude models.&lt;/p&gt;
&lt;p&gt;The multi-workspace, multi-process model feels unusual at first compared to a single editor flow, but once you handle the Rails-specific quirks, your ability to work in parallel and ship faster skyrockets.&lt;/p&gt;
&lt;p&gt;Here are the key practical steps and fixes I’ve found essential for getting a Rails app running reliably across multiple Conductor workspaces. I’ll try to keep this up to date as I find new things.&lt;/p&gt;
&lt;h2 id=&quot;copy-essential-gitignored-files&quot;&gt;Copy essential gitignore’d files&lt;/h2&gt;
&lt;p&gt;Anything in &lt;code&gt;.gitignore&lt;/code&gt; (credentials, &lt;code&gt;.env.local&lt;/code&gt;, custom config files, active storage files in &lt;code&gt;tmp/&lt;/code&gt; etc.) won’t copy automatically. Manually &lt;code&gt;cp&lt;/code&gt; these from your main &lt;code&gt;CONDUCTOR_ROOT&lt;/code&gt; project directory into each workspace directory during initial setup. This keeps isolation clean without git&lt;br&gt;
pollution.&lt;/p&gt;
&lt;h2 id=&quot;drive-everything-through-conductor_port&quot;&gt;Drive everything through &lt;code&gt;CONDUCTOR_PORT&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Conductor assigns dynamic ports—make your app fully aware of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prefer &lt;code&gt;ENV[&apos;CONDUCTOR_PORT&apos;]&lt;/code&gt; with a fallback (e.g., 3000).&lt;/li&gt;
&lt;li&gt;Update Action Mailer &lt;code&gt;default_url_options&lt;/code&gt;, controller/view host generation, and any internal URL building to use the current host + port.&lt;/li&gt;
&lt;li&gt;Scope sessions and cookies by host + port to prevent collisions across workspaces.&lt;/li&gt;
&lt;li&gt;A solid starting point for host and port handling is in &lt;a href=&quot;https://gist.github.com/jnunemaker/7e4b909bce0bb8796d342bed06bd4c3e&quot;&gt;in this Gist&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;isolate-databases-for-parallel-execution&quot;&gt;Isolate databases for parallel execution&lt;/h2&gt;
&lt;p&gt;Shared test databases cause lock errors when running specs/tests concurrently across workspaces. Solution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a per-workspace test database (suffix by workspace ID, &lt;code&gt;CONDUCTOR_PORT&lt;/code&gt;, or Rails test env number).&lt;/li&gt;
&lt;li&gt;Archive/drop the DB on workspace close.&lt;/li&gt;
&lt;li&gt;For dev, if you create new dbs, seed them in the conductor setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;use-setup-run-and-teardown-scripts&quot;&gt;Use setup, run, and teardown scripts&lt;/h2&gt;
&lt;p&gt;Conductor supports these hooks natively. Automate workspace preparation (DB setup, seeding, file copying), server startup, and cleanup. This makes spinning up and tearing down consistent, repeatable, and fast.&lt;/p&gt;
&lt;p&gt;Create &lt;code&gt;bin/conductor-setup&lt;/code&gt; (or just mod &lt;code&gt;bin/setup&lt;/code&gt; to work differently when conductor &lt;code&gt;ENV&lt;/code&gt; vars are present) and &lt;code&gt;bin/conductor-teardown&lt;/code&gt; (for when archive happens). &lt;code&gt;run&lt;/code&gt; can just be &lt;code&gt;bin/dev&lt;/code&gt; or whatever you use.&lt;/p&gt;
&lt;h2 id=&quot;cors-direct-uploads&quot;&gt;CORS (direct uploads)&lt;/h2&gt;
&lt;p&gt;One other issue is direct uploads to S3 and similar services. You’ll want to be sure to use a dev bucket that allows any host/port.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;aws&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; s3api&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; put-bucket-cors&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; --bucket&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; your-bucket-dev&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; --region&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; us-east-1&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; --cors-configuration&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;  &quot;CORSRules&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      &quot;AllowedHeaders&quot;: [&quot;*&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      &quot;AllowedMethods&quot;: [&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;HEAD&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      &quot;AllowedOrigins&quot;: [&quot;*&quot;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      &quot;MaxAgeSeconds&quot;: 3000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;rename-browser-tabs&quot;&gt;Rename browser tabs&lt;/h2&gt;
&lt;p&gt;Last but not least, rename your browser tabs. Trying to remember which port goes to which workspace is annoying. Just rename it once you open it to whatever the workspace is and you’ll be golden.&lt;/p&gt;
&lt;h2 id=&quot;the-shift&quot;&gt;The Shift&lt;/h2&gt;
&lt;p&gt;The mindset shift to multiple concurrent processes and workspaces is the biggest hurdle. Once it clicks and your app behaves correctly across them, the productivity gain is massive. If you run into Rails-specific issues (subdomains, shared resources, etc.), the fixes are usually straightforward. Just ask claude/conductor how to fix them by updating the bin scripts I mentioned above.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Conductor + Claude Opus has mostly replaced my editor for day-to-day work. I only fall back to a traditional editor for quick muscle-memory tasks.&lt;/li&gt;
&lt;li&gt;The GitHub flow of Create PR &amp;gt; Run CI &amp;gt; Fix CI Failures &amp;gt; Merge is SICK.&lt;/li&gt;
&lt;li&gt;Every support request or thought that pops in my head becomes a workspace.&lt;/li&gt;
&lt;li&gt;Heavy usage quickly justifies upgrading your Claude plan if you’re on a lighter tier. The combination of claude + conductor unlocks a lotta throughput.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re building with Rails and leaning on Claude heavily, Conductor is worth the initial setup effort. The payoff in shipping speed is real.&lt;/p&gt;
&lt;p&gt;Always happy to brainstorm fixes for any Rails gotchas you hit—just reach out.&lt;/p&gt;
</content:encoded></item><item><title>Rooted &amp; Reaching Podcast Interview</title><link>https://www.johnnunemaker.com/rooted-reaching-podcast-interview/</link><guid isPermaLink="true">https://www.johnnunemaker.com/rooted-reaching-podcast-interview/</guid><description>I recently sat down with Marty Mechtenberg on the Rooted and Reaching podcast to talk about my entrepreneurial journey, from struggling through my first…</description><pubDate>Fri, 23 Jan 2026 19:33:23 GMT</pubDate><content:encoded>&lt;iframe src=&quot;https://www.youtube.com/embed/nHBXcz-U9WA?feature=oembed&quot; title=&quot;Rooted &amp; Reaching: Building with Intention: Craft, Code, and Community with John Nunemaker&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;I recently sat down with Marty Mechtenberg on the Rooted and Reaching podcast to talk about my entrepreneurial journey, from struggling through my first programming class at Bethel to building multiple SaaS businesses while staying rooted in the South Bend area.&lt;/p&gt;
&lt;h2 id=&quot;the-early-days-learning-to-code-the-hard-way&quot;&gt;The Early Days: Learning to Code (the Hard Way)&lt;/h2&gt;
&lt;p&gt;I wasn’t a natural programmer. In my sophomore year at Bethel, I was pulling a D in my programming class while my future business partner Steve would walk in, hand in his homework, and leave. I spent hours in office hours, terrified of failure after a lifetime of straight A’s. But that struggle taught me something important: once I learned something the hard way, it stuck.&lt;/p&gt;
&lt;p&gt;What really hooked me was the internet. The moment I could make something and have my mom see it by typing a URL into her browser, that was magic. That’s when programming stopped being abstract loops on a screen and became something I couldn’t stop doing.&lt;/p&gt;
&lt;h2 id=&quot;ordered-list-and-the-power-of-sharing&quot;&gt;Ordered List and the Power of Sharing&lt;/h2&gt;
&lt;p&gt;After Notre Dame, I joined my friend Steve Smith at Ordered List, a small agency. We had a simple philosophy: talk about everything we built. We blogged about our process, spoke at conferences, and contributed to open source. We weren’t trying to be clever about marketing, it was just what we knew how to do.&lt;/p&gt;
&lt;p&gt;That openness led to opportunities we never expected. Speaking at a Ruby conference in New Orleans, I reconnected with a friend who had started GitHub. One conversation led to another, and eventually GitHub acquired our five-person team in 2011.&lt;/p&gt;
&lt;h2 id=&quot;the-github-years-and-why-i-left&quot;&gt;The GitHub Years (and Why I Left)&lt;/h2&gt;
&lt;p&gt;GitHub was an incredible ride—from 45 employees to over a thousand in just four years. But as the company grew, so did the layers between me and any real decision-making power. By 2016-17, I was seven layers deep and itching to get back to building things my own way.&lt;/p&gt;
&lt;p&gt;When Microsoft acquired GitHub in 2019, I took my exit. I needed to reclaim my indie spirit.&lt;/p&gt;
&lt;h2 id=&quot;building-for-flexibility-not-fortune&quot;&gt;Building for Flexibility, Not Fortune&lt;/h2&gt;
&lt;p&gt;Today I run multiple products: Box Out Sports (sports graphics for schools), Flipper (feature flags for developers), and Fireside (podcast hosting). The common thread? I’ve always optimized for flexibility over maximum revenue.&lt;/p&gt;
&lt;p&gt;I watched my dad work 90-hour weeks on the farm. I wanted the freedom to take a seven-week camping trip in the summer.&lt;/p&gt;
&lt;p&gt;My advice for aspiring entrepreneurs: work your tail off in your 20s across different environments. Figure out what you actually like. Then optimize for the life you want, not just the biggest paycheck.&lt;/p&gt;
&lt;h2 id=&quot;why-south-bend&quot;&gt;Why South Bend?&lt;/h2&gt;
&lt;p&gt;People ask why I didn’t move to San Francisco. The answer is simple: South Bend has everything I need. Good restaurants, Notre Dame athletics, a reasonable cost of living, no brutal traffic. It’s big enough to have what matters, small enough to avoid what doesn’t.&lt;/p&gt;
&lt;p&gt;Plus, there’s something special here I didn’t expect to find: South Bend had its own watch company in the early 1900s, making precision pocket watches for the railroad industry. I’ve become obsessed with finding and restoring these watches—physical artifacts I can pass down to my kids, unlike software that rots in three years without constant care.&lt;/p&gt;
&lt;h2 id=&quot;the-ai-era&quot;&gt;The AI Era&lt;/h2&gt;
&lt;p&gt;I’m not worried about AI taking over. I’m obsessed with it. It’s made me multilingual as a programmer. I can now contribute to Go projects despite being a Ruby developer for 20 years. The fundamentals I learned the hard way? They translate perfectly to telling Claude or ChatGPT exactly what I need.&lt;/p&gt;
&lt;p&gt;The key is knowing what to ask for. Twenty years of programming principles don’t disappear, they become the foundation for working with AI effectively.&lt;/p&gt;
&lt;p&gt;Hope you enjoy!&lt;/p&gt;
</content:encoded><category>business</category><category>podcast</category></item><item><title>What makes you different</title><link>https://www.johnnunemaker.com/what-makes-you-different/</link><guid isPermaLink="true">https://www.johnnunemaker.com/what-makes-you-different/</guid><description>Something that&apos;s been on my mind lately: Why is a customer going to choose my product? There&apos;s tens to hundreds or even thousands (looking at you CRM&apos;s) of…</description><pubDate>Mon, 13 Oct 2025 14:39:27 GMT</pubDate><content:encoded>&lt;p&gt;Something that’s been on my mind lately: &lt;strong&gt;Why is a customer going to choose my product&lt;/strong&gt;? There’s tens to hundreds or even thousands (looking at you CRM’s) of every app for every problem. Why would someone choose mine?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Price?&lt;/strong&gt; I’m a &lt;a href=&quot;https://blog.flippercloud.io/per-seat-pricing-sucks/&quot;&gt;firm believer&lt;/a&gt; in charging a price that works for the customer &lt;strong&gt;and&lt;/strong&gt; for you. Competing on price is a race to the bottom. If this is where you are at, you’ve already lost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Features?&lt;/strong&gt; Maybe but that’s a grind and a bit whack-a-mole. Having all the features someone could ever want waters down a product. Honestly, it makes products confusing. How do you build work flow and create value for the customer when the customer is anyone and the work flow is anything?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Something else?&lt;/strong&gt; I’m becoming a bit obsessed with what makes you different. &lt;em&gt;People remember different&lt;/em&gt;. And when someone gets out their card to buy something, you want them to remember you.&lt;/p&gt;
&lt;p&gt;Our brains love to live on autopilot in a sea of sameness. This makes us hyper-sensitive to different. Even if that weren’t true, different is great because it makes comparison difficult. Instead of granny smith verse golden delicious, it’s apples and oranges.&lt;/p&gt;
&lt;h2 id=&quot;the-hot-dog&quot;&gt;The Hot Dog&lt;/h2&gt;
&lt;p&gt;In Unreasonable Hospitality, Will Guidara recounts the story of one particular table of diners at Eleven Madison. They were foodies in New York from overseas to enjoy the best restaurants.&lt;/p&gt;
&lt;p&gt;They’d probably had several amazing experiences. But one of them mentioned &lt;strong&gt;they were bummed they didn’t get a new york hot dog&lt;/strong&gt;. This was their last night in the city, so Will ran to the nearest hot dog cart, got a dog and convinced his chef to serve it to them.&lt;/p&gt;
&lt;p&gt;Of all the places they dined, who do you think they remembered? Which story do you think they told people over and over? The hot dog.&lt;/p&gt;
&lt;p&gt;Different is remembered. Different is shared. It’s better than being better. And that’s why I can’t stop thinking about what makes my products different.&lt;/p&gt;
&lt;h2 id=&quot;an-example&quot;&gt;An Example&lt;/h2&gt;
&lt;p&gt;Let’s talk about &lt;a href=&quot;https://boxoutsports.com&quot;&gt;Box Out&lt;/a&gt; for a moment as a practical example.&lt;/p&gt;
&lt;p&gt;Our competition focuses on &lt;em&gt;infinite customization&lt;/em&gt;. Move this logo wherever you want or make it bigger. Change the font or text size. Drag this. Drop that. All of them just re-create Photoshop in the browser with a bunch of stock templates to choose from. Heck, that is all canva is.&lt;/p&gt;
&lt;p&gt;We do the opposite. We focus on &lt;strong&gt;speed&lt;/strong&gt; (time to social timeline) and &lt;strong&gt;brand&lt;/strong&gt; (&lt;a href=&quot;https://boxoutsports.com/features/manage-users-with-set-it-and-forget-it-permissions&quot;&gt;delegate&lt;/a&gt; to students/interns but retain quality). How fast can we help you make a graphic to share with your followers (and then move on with your life).&lt;/p&gt;
&lt;p&gt;Don’t move things around, it’s wasting time.&lt;br&gt;
Lock down colors, fonts and templates.&lt;br&gt;
Don’t resize logos or text.&lt;/p&gt;
&lt;p&gt;We’re different. Those differences create constraints that actually guide us to build a better product. Sure, we’ve been tempted to build drag and drop, but that would be a disservice to our customers. Their time is better spent on other things.&lt;/p&gt;
&lt;h3 id=&quot;trade-offs&quot;&gt;Trade offs&lt;/h3&gt;
&lt;p&gt;Everything is trade offs. Things like the list below would be dramatically harder or impossible if we allowed that.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://boxoutsports.com/features/multi-format-graphics-create-once-get-three-formats-automatically&quot;&gt;&lt;strong&gt;Multi-format&lt;/strong&gt;&lt;/a&gt;. Different platforms prefer different sized graphics. We handle that for you. X gets widescreen, IG 4:5 (or now 3:4), Story format. &lt;em&gt;Fill out your data once and hit share&lt;/em&gt;. Don’t duplicate and resize and move stuff around.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://boxoutsports.com/features/generate-personalized-captions-with-ai-suggestions&quot;&gt;&lt;strong&gt;Suggestions&lt;/strong&gt;&lt;/a&gt;. Not sure what to say? Hit a button and get 5 options with different tones that &lt;em&gt;smell quite similar&lt;/em&gt; to your previous shares. Of course they are based on the graphic you are attaching to the share.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-ins&lt;/strong&gt;. Need to &lt;a href=&quot;https://boxoutsports.com/features/edit-your-images-in-a-flash&quot;&gt;crop an image&lt;/a&gt; or &lt;a href=&quot;https://boxoutsports.com/features/effortlessly-edit-and-trim-videos&quot;&gt;trim a video&lt;/a&gt;? No problem. Do it right in the browser, right from the graphic you are creating. &lt;em&gt;No need to leave the browser&lt;/em&gt;. We’ll even &lt;a href=&quot;https://boxoutsports.com/features/automatically-remove-the-background-from-images&quot;&gt;remove the background&lt;/a&gt; for a stunning PNG cut out on many graphics. Not ready to share yet? &lt;a href=&quot;https://boxoutsports.com/features/planning-social-media-is-easy-with-scheduled-posts&quot;&gt;Schedule&lt;/a&gt; it for later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autofill&lt;/strong&gt;. We autofill fields for the graphic based on what you’ve previously used, &lt;a href=&quot;https://boxoutsports.com/features/sync-your-schedule-create-graphics-instantly&quot;&gt;upcoming events&lt;/a&gt;, your linked brand information, &lt;a href=&quot;https://boxoutsports.com/features/grab-any-team-logo&quot;&gt;opponents&lt;/a&gt; linked brand information, live stats, etc. Autofill means &lt;em&gt;fewer fields to fill out&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt;. We just spent months decreasing average graphic render time from 2.9 to 1.1 seconds. Rendering is so fast now that it feels live.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For Box Out, it’s easy to define and see the value those differences create for us (and our customers). Now it’s time to think what those differences are and will be for &lt;a href=&quot;https://fireside.fm&quot;&gt;Fireside&lt;/a&gt; and &lt;a href=&quot;https://www.flippercloud.io&quot;&gt;Flipper&lt;/a&gt;…&lt;/p&gt;
</content:encoded><category>business</category><category>thoughts</category></item><item><title>Self-Made is a Myth Podcast</title><link>https://www.johnnunemaker.com/self-made-is-a-myth-podcast/</link><guid isPermaLink="true">https://www.johnnunemaker.com/self-made-is-a-myth-podcast/</guid><description>Podcast interview where I share my journey building lasting SaaS businesses, the power of partnerships, and why I&apos;m obsessed with onboarding, risk reduction, and hiring ‘batteries included’ people.</description><pubDate>Wed, 01 Oct 2025 15:19:04 GMT</pubDate><content:encoded>&lt;p&gt;I’m never one to turn down being on a podcast, especially since acquiring &lt;a href=&quot;https://fireside.fm&quot;&gt;Fireside.fm&lt;/a&gt;, a podcast host. So yesterday, I hopped on a call with Tim Campsall to chat about running &lt;a href=&quot;https://boxoutsports.com&quot;&gt;Box Out&lt;/a&gt; and &lt;a href=&quot;https://verygoodsoftware.company&quot;&gt;Very Good&lt;/a&gt;. And the crazy thing is it’s already out on Youtube. I wasn’t in my home office so my background was bleak and my audio was subpar but still a great conversation.&lt;/p&gt;
&lt;iframe src=&quot;https://www.youtube.com/embed/14OZttvB-24?feature=oembed&quot; title=&quot;John Nunemaker - Box Out Sports: Self Made Is A Myth Interview&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;highlights&quot;&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Shortest path between builders and users is critical.&lt;/li&gt;
&lt;li&gt;Hire “batteries included” people who are self-driven.&lt;/li&gt;
&lt;li&gt;I’m not really good at anything specific. I’m just good at being glue.&lt;/li&gt;
&lt;li&gt;Measure progress (time tracking) and celebrate wins to avoid burnout.&lt;/li&gt;
&lt;li&gt;Design products to save people time. Design your business to free your own time.&lt;/li&gt;
&lt;li&gt;People buy from people. My dream as a developer is to build a good product and never talk to anyone, but it just doesn’t work that way.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>podcast</category><category>business</category></item><item><title>Giant Robots</title><link>https://www.johnnunemaker.com/giant-robots/</link><guid isPermaLink="true">https://www.johnnunemaker.com/giant-robots/</guid><description>A few weeks back I joined Chad on his Giant Robots podcast to talk Ruby, Rails and a bit of business – including our acquisition of Fireside last year. The…</description><pubDate>Thu, 29 May 2025 14:19:23 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks back I &lt;a href=&quot;https://podcast.thoughtbot.com/576&quot;&gt;joined Chad on his Giant Robots podcast&lt;/a&gt; to talk Ruby, Rails and a bit of business – including our &lt;a href=&quot;https://www.johnnunemaker.com/acquiring-fireside/&quot;&gt;acquisition of Fireside&lt;/a&gt; last year. The episode just went live and I hope you enjoy it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fun fact&lt;/strong&gt;: their podcast is actually hosted on &lt;a href=&quot;https://fireside.fm/&quot;&gt;Fireside&lt;/a&gt; – inception! You can listen wherever you consume podcasts or on Fireside (below) or &lt;a href=&quot;https://www.youtube.com/watch?v=URrk9eigXsI&quot;&gt;youtube&lt;/a&gt;.&lt;/p&gt;
&lt;iframe src=&quot;https://player.fireside.fm/v2/DiNRb69N+NgFmOaBl?theme=dark&quot; title=&quot;Embedded content&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
</content:encoded><category>podcast</category></item><item><title>Rails Business Podcast</title><link>https://www.johnnunemaker.com/rails-business-podcast/</link><guid isPermaLink="true">https://www.johnnunemaker.com/rails-business-podcast/</guid><description>I had a nice chat with Brendan and Ryan on the Rails Business podcast a few weeks ago and now its live! Search for &quot;Rails Business&quot; in your favorite podcast…</description><pubDate>Tue, 15 Apr 2025 20:28:09 GMT</pubDate><content:encoded>&lt;p&gt;I had a nice chat with Brendan and Ryan on the &lt;a href=&quot;https://www.railsbusiness.com/&quot;&gt;Rails Business podcast&lt;/a&gt; a few weeks ago and now its live! Search for “Rails Business” in your favorite podcast app or you can listen at the following link:&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;bookmark&quot; href=&quot;https://www.railsbusiness.com/2435951/episodes/16978258-john-nunemaker&quot;&gt;&lt;span class=&quot;bookmark-title&quot;&gt;John Nunemaker - Rails Business&lt;/span&gt;&lt;span class=&quot;bookmark-description&quot;&gt;In this episode, Brendan and Ryan welcome John Nunemaker.&amp;amp;nbsp; John is a prominent figure in the Ruby community and, shares his journey into Ruby on Rails, his career evolution, and his unique approach to managing multiple projects simultaneously…&lt;/span&gt;&lt;span class=&quot;bookmark-domain&quot;&gt;railsbusiness.com&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want the tldr, we cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;my journey into Ruby and Rails&lt;/li&gt;
&lt;li&gt;my current projects and Ruby usage&lt;/li&gt;
&lt;li&gt;the role of AI in my dev process&lt;/li&gt;
&lt;li&gt;how to balance multiple projects&lt;/li&gt;
&lt;li&gt;time tracking and productivity&lt;/li&gt;
&lt;li&gt;consistency across projects&lt;/li&gt;
&lt;li&gt;the origin and success of Box Out&lt;/li&gt;
&lt;li&gt;future plans&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>podcast</category></item><item><title>Ruby on Rails Podcast: Taking over Fireside.fm</title><link>https://www.johnnunemaker.com/ruby-on-rails-podcast-taking-over-fireside-fm/</link><guid isPermaLink="true">https://www.johnnunemaker.com/ruby-on-rails-podcast-taking-over-fireside-fm/</guid><description>I was stoked when I discovered that the Ruby on Rails podcast is hosted on our platform – fireside.fm. So I reached out to Elise for feedback and to…</description><pubDate>Wed, 22 Jan 2025 21:13:43 GMT</pubDate><content:encoded>&lt;p&gt;I was stoked when I discovered that the &lt;a href=&quot;https://www.therubyonrailspodcast.com&quot;&gt;Ruby on Rails podcast&lt;/a&gt; is hosted on our platform – &lt;a href=&quot;https://fireside.fm&quot;&gt;fireside.fm&lt;/a&gt;. So I reached out to Elise for feedback and to volunteer Garrett and I for an episode.&lt;/p&gt;
&lt;p&gt;We recorded last week and its &lt;a href=&quot;https://www.therubyonrailspodcast.com/531&quot;&gt;already live today&lt;/a&gt;. Our chat covered taking over an outdated Rails app and all the progress we’ve made modernizing it the past few months. We even threw in a few stories of battle scars earned.&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;iframe src=&quot;https://player.fireside.fm/v2/3OC19MC9+qjmEkYf2?theme=dark&quot; title=&quot;Embedded content&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;P.S. It’s been &lt;a href=&quot;https://www.therubyonrailspodcast.com/289&quot;&gt;5 years since I was last on&lt;/a&gt; the rails pod (Brittany Martin was the host).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2026-10-01&lt;/strong&gt;: Two years in, almost nothing from the outdated app we took over is left. Garrett and I told the rest of the story, including Postgres 9.5 to 18 in one hop, in episode one of our new podcast, &lt;a href=&quot;https://www.johnnunemaker.com/the-friday-deploy/&quot;&gt;The Friday Deploy&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>podcast</category></item><item><title>Episode #4</title><link>https://www.johnnunemaker.com/episode-4/</link><guid isPermaLink="true">https://www.johnnunemaker.com/episode-4/</guid><description>It&apos;s out and it&apos;s stout. Actually, I don&apos;t know if it&apos;s stout or not. I just like to rhyme (all the time). My 3 Fav Quotes “What if instead of discounts, we…</description><pubDate>Thu, 09 Jan 2025 21:47:52 GMT</pubDate><content:encoded>&lt;p&gt;It’s out and it’s stout. Actually, I don’t know if it’s stout or not. I just like to rhyme (all the time).&lt;/p&gt;
&lt;h2 id=&quot;my-3-fav-quotes&quot;&gt;My 3 Fav Quotes&lt;/h2&gt;
&lt;p&gt;“What if instead of discounts, we focused on lowering the risk for customers to try podcasting?” – Garrett&lt;/p&gt;
&lt;p&gt;“You don’t have to go crazy and work a ton. You can you can achieve great success by turning a few levers. But sometimes it just takes a lot of work to figure out what those levers are.” – John&lt;/p&gt;
&lt;p&gt;“I’m just thinking about swag, but that’s a discussion from another day.” – Kris&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;bookmark&quot; href=&quot;https://blog.fireside.fm/black-friday-reflections-and-new-year-opportunities/&quot;&gt;&lt;span class=&quot;bookmark-title&quot;&gt;Black Friday Reflections and New Year Opportunities&lt;/span&gt;&lt;span class=&quot;bookmark-description&quot;&gt;I was hoping to get this out last year, but holidays got in the way of that (in a good way). So here is the last episode recorded in 2024, fresh for you to enjoy in 2025. * Black Friday Reflections and Trends * Rethinking Sales Strategies for Software * Technical Upgrades and&lt;/span&gt;&lt;span class=&quot;bookmark-domain&quot;&gt;blog.fireside.fm&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Big shoutout to &lt;a href=&quot;https://riverside.fm&quot;&gt;Riverside&lt;/a&gt;. We use them to record and edit this. And I’ve found it pretty fun the past couple times.&lt;/p&gt;
</content:encoded><category>podcast</category></item><item><title>Episode #3</title><link>https://www.johnnunemaker.com/episode-3/</link><guid isPermaLink="true">https://www.johnnunemaker.com/episode-3/</guid><description>For those that haven&apos;t subscribed to the fireside blog or the podcast, I figured I&apos;d update here as well. We released episode #3 on Mastering Prioritization…</description><pubDate>Tue, 31 Dec 2024 19:33:13 GMT</pubDate><content:encoded>&lt;p&gt;For those that haven’t &lt;a href=&quot;https://blog.fireside.fm/#/portal/signup&quot;&gt;subscribed to the fireside blog&lt;/a&gt; or the &lt;a href=&quot;https://standinginthefire.fireside.fm/subscribe&quot;&gt;podcast&lt;/a&gt;, I figured I’d update here as well. We released episode #3 on &lt;a href=&quot;https://standinginthefire.fireside.fm/3&quot;&gt;Mastering Prioritization in SaaS&lt;/a&gt; last week. Give it a listen and subscribe in your favorite podcast app. At some point I’ll get too lazy to post here too. 😄&lt;/p&gt;
&lt;p&gt;One of my favorite parts was the segment on time tracking, something that I’ve found useful of late.&lt;/p&gt;
&lt;iframe src=&quot;https://player.fireside.fm/v2/26YjJHTb+YBzaOy8I?theme=dark&quot; title=&quot;Embedded content&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
</content:encoded><category>podcast</category></item><item><title>Episode #2</title><link>https://www.johnnunemaker.com/episode-2/</link><guid isPermaLink="true">https://www.johnnunemaker.com/episode-2/</guid><description>Episode #1 went out last Thursday. Now here we are on Tuesday and ANOTHER EPISODE FOR YOU. I know right?! Christmas came early this year. On today&apos;s…</description><pubDate>Tue, 17 Dec 2024 13:41:23 GMT</pubDate><content:encoded>&lt;p&gt;Episode #1 went out last Thursday. Now here we are on Tuesday and &lt;strong&gt;ANOTHER EPISODE FOR YOU&lt;/strong&gt;. I know right?! Christmas came early this year.&lt;/p&gt;
&lt;p&gt;On today’s episode, we hit on a few topics:&lt;/p&gt;
&lt;p&gt;🙋 Customer feedback.&lt;br&gt;
😖 Podcasting pains.&lt;br&gt;
🆕 Future features.&lt;br&gt;
🏆 Recent wins.&lt;/p&gt;
&lt;p&gt;Just hit play… you know you want to. And then be sure to &lt;a href=&quot;https://standinginthefire.fireside.fm/subscribe&quot;&gt;subscribe&lt;/a&gt;.&lt;/p&gt;
&lt;iframe src=&quot;https://player.fireside.fm/v2/26YjJHTb+k0zrWQdh?theme=dark&quot; title=&quot;Embedded content&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;In the interest of getting better, we’re improving something each episode. Today’s improvement was a wee bit of intro/outro music.&lt;/p&gt;
</content:encoded><category>podcast</category></item><item><title>Episode #1</title><link>https://www.johnnunemaker.com/episode-1/</link><guid isPermaLink="true">https://www.johnnunemaker.com/episode-1/</guid><description>We did it! The Fireside crew went through the entire process of setting up a podcast and recording our first episodes. #1 is live wherever you enjoy…</description><pubDate>Thu, 12 Dec 2024 15:05:32 GMT</pubDate><content:encoded>&lt;p&gt;We did it! The &lt;a href=&quot;https://fireside.fm&quot;&gt;Fireside&lt;/a&gt; crew went through the entire process of setting up a podcast and recording our first episodes.&lt;/p&gt;
&lt;p&gt;#1 is live wherever you enjoy podcasts (probably).&lt;/p&gt;
&lt;iframe src=&quot;https://player.fireside.fm/v2/26YjJHTb+3MIL6f6q?theme=dark&quot; title=&quot;Embedded content&quot; loading=&quot;lazy&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;I never would have guessed how tedious it would be to get your first episode out the door. Now I have a ton of respect for our customers, especially our non-technical ones, who have made it through this gauntlet.&lt;/p&gt;
&lt;p&gt;So much information to fill out! I’m looking forward to getting this process locked down so new episodes will be much easier. Also, planning to start paying for an editor. 🤣&lt;/p&gt;
&lt;p&gt;We set the quality bar very low for this. We know we’ll get better with each episode but we had to start somewhere. Enjoy!&lt;/p&gt;
&lt;p&gt;And be sure to &lt;a href=&quot;https://standinginthefire.fireside.fm/subscribe&quot;&gt;subscribe&lt;/a&gt;!&lt;/p&gt;
</content:encoded><category>podcast</category></item><item><title>Shrinking a Postgres Table</title><link>https://www.johnnunemaker.com/shrinking-a-postgres-table/</link><guid isPermaLink="true">https://www.johnnunemaker.com/shrinking-a-postgres-table/</guid><description>How I used a PostgreSQL table swap to cut disk usage from 87% to 53% in about 10 seconds, with only a few seconds of possible data loss.</description><pubDate>Thu, 28 Nov 2024 14:10:17 GMT</pubDate><content:encoded>&lt;h2 id=&quot;in-short&quot;&gt;In short&lt;/h2&gt;
&lt;p&gt;I shrank a 57 GB PostgreSQL table by cloning its structure, copying only the rows worth keeping, briefly renaming the old and new tables, and then dropping the original. The swap took about 10 seconds and reduced server disk use from 87% to 53%.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Identify the table and rows consuming the space.&lt;/li&gt;
&lt;li&gt;Create a replacement with &lt;code&gt;CREATE TABLE ... (LIKE ... INCLUDING ALL)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copy only the rows you need into the replacement.&lt;/li&gt;
&lt;li&gt;Rename the old and new tables during a short maintenance window.&lt;/li&gt;
&lt;li&gt;Validate the replacement, then drop the old table to reclaim disk space.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This was a calculated tradeoff for this application. Before using the technique, verify your constraints, indexes, sequences, dependencies, write traffic, and recovery plan against your own schema.&lt;/p&gt;
&lt;p&gt;Ok folks, this is kind of a weird one. I’m going to put it in the “you won’t ever need this, but if you do, you are going to be glad I wrote this up for ya” category.&lt;/p&gt;
&lt;p&gt;As you may or may not know, I recently &lt;a href=&quot;https://www.johnnunemaker.com/acquiring-fireside/&quot;&gt;acquired fireside.fm&lt;/a&gt;, an awesome podcast host. While investigating a slow query, I noticed that the database servers were at 87% disk space use.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ df -h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Filesystem      Size  Used Avail Use% Mounted on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/dev/root       158G  129G   21G  87% /&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I typically use a managed Postgres, which makes it real obvious when you are starting to use a lot of disk space and easy to fix. But this is self-managed Postgres on Linode.&lt;/p&gt;
&lt;p&gt;Servers don’t like to run out of disk space (or even get close to running out).&lt;/p&gt;
&lt;h2 id=&quot;what-is-taking-up-the-space&quot;&gt;What is taking up the space?&lt;/h2&gt;
&lt;p&gt;So I started up a casual chat with GitHub’s Copilot. You know, the typical, “how are you?”, “oh I’m fine, you?”, “yeah, same”, “do you like long walks on the beach?”, “yeah, my database is about to fall over too”.&lt;/p&gt;
&lt;p&gt;I asked Copilot how I could quickly get the size of the database:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; pg_size_pretty(pg_database_size(current_database())) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; database_size;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#6A737D&quot;&gt;-- 114 GB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, my database was the problem, not some rogue log file or backup retention policy.&lt;/p&gt;
&lt;p&gt;My next question was which table is taking up all the space:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    table_name, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    pg_size_pretty(pg_total_relation_size(table_name)) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; total_size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    information_schema&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;tables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    table_schema &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;public&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    pg_total_relation_size(table_name) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;DESC&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The usual suspects filled up the top 10 (analytics/metrics):&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;            table_name&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;             |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; total_size&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;-----------------------------------+------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; downloads&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;                         |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; 57&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; GB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; downloads_archive&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;                 |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; 30&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; GB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; day_downloads&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;                     |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; 12&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; GB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; location_downloads&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;                |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; 6235&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; MB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; metrics&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;                           |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; 3537&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; MB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; user_agent_downloads&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;              |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; 2145&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; MB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; spotify_day_downloads&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;             |&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; 2072&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; MB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;downloads&lt;/code&gt; seemed the largest so I started there. I wasn’t entirely sure how the table was used (again I’m new to this app). So I poked around for min and max &lt;code&gt;created_at&lt;/code&gt; times. To my surprise, there were rows going back to 2021.&lt;/p&gt;
&lt;h2 id=&quot;why-is-the-downloads-table-large&quot;&gt;Why is the downloads table large?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;downloads&lt;/code&gt; table seemed to be a record of all the raw downloads before they are aggregated into tables more efficient for querying in a web request. As downloads are processed, they are deleted from the table.&lt;/p&gt;
&lt;p&gt;The problem was many records (10’s of thousands an hour) were not being processed and thus not being deleted. Those downloads seemed to be for episodes that no longer exist for various reasons.&lt;/p&gt;
&lt;p&gt;Over the course of many years, this added up to many 10’s of millions of rows and 35% of the disk space. I sliced and diced several queries and determined that every download older than a few hours was effectively bad data and not processable.&lt;/p&gt;
&lt;p&gt;Now I knew what my issue was, but I wasn’t sure about the best fix.&lt;/p&gt;
&lt;h2 id=&quot;how-to-shrink-the-table-with-a-table-swap&quot;&gt;How to shrink the table with a table swap&lt;/h2&gt;
&lt;p&gt;If you don’t know this already, Copilot is great for brainstorming stuff like this. Talk human to it and it’ll throw ideas back to you.&lt;/p&gt;
&lt;p&gt;Copilot really wanted me to delete the rows in batches of 1-10k and then &lt;code&gt;vacuum&lt;/code&gt; at the end. But not &lt;code&gt;vacuum full&lt;/code&gt; because that would lock the database and bring down the house.&lt;/p&gt;
&lt;p&gt;I ran the first query to delete 1k records:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;WITH&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; rows_to_delete &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    SELECT&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    FROM&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; created_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;2022-01-01&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;    LIMIT&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;DELETE&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;IN&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; rows_to_delete);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But it took like 20 seconds.&lt;/p&gt;
&lt;p&gt;I wasn’t going to sit around and run this 30 - 50 thousand more times.&lt;/p&gt;
&lt;p&gt;I also didn’t really feel like making a background job to handle this for me.&lt;/p&gt;
&lt;p&gt;Additionally, copilot wasn’t sure how much space would actually be freed up by a &lt;code&gt;vacuum&lt;/code&gt; (as compared to a &lt;code&gt;vacuum full&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The easiest, fastest and most guaranteed way to free up space for a table is to drop it. But I did want some data out of it. That’s when I remembered table swapping.&lt;/p&gt;
&lt;p&gt;I asked Copilot for the best way to clone the downloads table:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt; downloads_new&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;LIKE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads INCLUDING ALL);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🆒&lt;/p&gt;
&lt;p&gt;And how do I insert only the rows I need, those that are downloads for episodes that exist:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads_new &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  SELECT&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; d.&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads d &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  INNER JOIN&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; episodes e &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; d&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;episode_token&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;  WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; date_trunc(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;day&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt;created_at&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;2024-11-27&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;😎&lt;/p&gt;
&lt;p&gt;And lastly, how do I swap the tables:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads RENAME &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads_old;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;ALTER&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads_new RENAME &lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;TO&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sure, I could have looked these up in the Postgres docs or on Stack Overflow, but the ability to paste in your schema for your table and say in English what you want to do and get exact commands to copy is just amazing.&lt;/p&gt;
&lt;p&gt;The only bit left was to drop the original table with all the junk data:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt;DROP&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#E1E4E8&quot;&gt; downloads_old;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, I had a brand, spanking new &lt;code&gt;downloads&lt;/code&gt; table with only 30-40k records (past hour or so of downloads) and a whole lot more disk space:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; df&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#79B8FF&quot;&gt; -h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Filesystem&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      Size&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;  Used&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; Avail&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; Use%&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; Mounted&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; on&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#B392F0&quot;&gt;/dev/root&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;       158G&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;   80G&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;   71G&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt;  53%&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#9ECBFF&quot;&gt; /&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The best part is it took about 10 seconds to run the queries and I lost, at most, if anything, a few seconds of data during the data dump and table rename.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A worthy trade off compared to hours of coding and babysitting.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;brainstorming-with-real-people&quot;&gt;Brainstorming with Real People&lt;/h2&gt;
&lt;p&gt;Now, it’s worth mentioning that I wasn’t entirely sure if this was going to get me the result I hoped for (no downtime, low effort, and freed up space).&lt;/p&gt;
&lt;p&gt;So first, I dropped into my handy dandy, top secret slack of battle scarred programmers who are smarter than me.&lt;/p&gt;
&lt;p&gt;Akin to Copilot, I posed the same question and I got a variety of other options. I felt good about all of them, but none could compare (IMO) with the simplicity of the table swap.&lt;/p&gt;
&lt;p&gt;No one seemed overly scared that the table swap wouldn’t work. And I got a yes from two people who thought it would be easiest. That was enough to give me the confidence to try it first.&lt;/p&gt;
&lt;p&gt;Cheers to top secret slacks with smart people and AI chat bots! 🍻&lt;/p&gt;
&lt;p&gt;So that’s how I spent my Thanksgiving morning!&lt;/p&gt;
&lt;p&gt;And, honestly, I’m good with that. Feeling thankful even.&lt;/p&gt;
&lt;p&gt;I learned something new. I saved my database.&lt;/p&gt;
&lt;p&gt;And now I’m going to eat some great food and hang out with family sans a low undercurrent of stress wondering if my poor little database has crept from 87% to 88% disk usage or even higher!&lt;/p&gt;
</content:encoded><category>TIL</category></item></channel></rss>