← All entries

Sunday, May 10, 2026

Day 38 — The Blog Gets a Database

We've been building in public since day one, but publishing a post still required a git commit and a deploy. That felt wrong. Today we fixed it.

3 min read

Daily entry

Day 38of building EquinePilot

Currently Day 77 · founder build log

Milestones reached

  • Blog backed by Postgres
  • Admin authoring surface
  • Build pipeline decoupled from DB schema

There's a certain irony in running a build-in-public blog whose publishing workflow is itself a developer task. For five weeks, every post on this blog — every day-by-day account of what shipped and why — required the same ritual: write markdown, commit to git, push, wait for CI, wait for Vercel, check the deploy. Editing a typo after the fact meant opening a PR.

That was fine when we had a handful of posts and the blog was honestly an afterthought — a nice-to-have bolted onto the marketing site. It's less fine now that we're 38 days in with a real publication cadence and an operator (me) who wants to be able to fix a sentence without triggering a deploy pipeline.

So today the blog moved to Postgres.

The new journal_entries table is straightforward: slug, title, date, kind (daily/weekly/phase), body as markdown, and a publishedAt timestamp as the draft gate. Null means draft; set means published. The admin app at admin.equinepilot.com now has a full authoring surface — list view broken into Published and Drafts sections, a form for create and edit, and a toggle that flips a post from draft to live without touching the codebase.

From the outside, nothing changed. The blog URLs are the same, the RSS feed is the same, the rendering is the same — gray-matter parsed to HTML with our Georgia headings and Paddock Green link accents. The data just comes from the database now instead of from the filesystem.

There were a couple of wrinkles worth documenting.

The first was the build pipeline. Next.js, by default, will try to run data queries during the "Collecting page data" step at build time for pages that don't explicitly opt out. That worked fine when the blog was reading from the filesystem — filesystem access at build time is cheap and always-available. But now that the blog queries Postgres, any Neon preview branch that doesn't have the latest migration applied would fail the web app build. The fix is straightforward (export const dynamic = 'force-dynamic' on the three blog routes), but the lesson is worth noting: static generation and external DB dependencies don't compose cleanly unless you're very intentional about it. The marketing blog doesn't need sub-second edge delivery — it's a dev blog, not a news site. SSR plus Vercel's edge cache is completely adequate, and we get build isolation for free.

The second was the backfill. Thirty-eight days of posts live in markdown files in the repo. Rather than migrate by hand, we wrote db:import-journal — a script that reads the existing files, parses the frontmatter, and upserts into journal_entries. Idempotent, runnable from the repo root as pnpm db:import-journal. The backfilled boolean on each row lets us distinguish imported posts from ones authored natively in the admin, which matters for auditing and for knowing which posts might have minor formatting quirks from the original markdown.

The bigger thing this represents, though, is infrastructure maturity. At the beginning, moving fast means accepting rough edges — a blog that requires a deploy is acceptable when you're shipping five features a day and the blog is the least important thing. But those rough edges accumulate, and eventually the blog becomes part of the product rather than an appendage to it. When that happens, it deserves the same treatment as everything else: live-editable, no-deploy workflow, accessible to anyone with admin access rather than just anyone with a git push.

That's the thing about building incrementally in public — the act of narrating the build eventually reveals which parts of your own infrastructure you haven't built properly yet. The blog told on itself.

Tomorrow we're back to product work. CourseWalk Week 1 implementation is queued up.