I brew kombucha

If you haven’t fallen down this hole: kombucha is sweet tea fermented by a SCOBY (a rubbery pancake of yeast and bacteria) into something tart and fizzy. It’s a living hobby — the culture is alive, every batch is a little different, and the only way to get good is to pay attention and remember what you did.

I was not remembering what I did. Brew dates lived in my head, taste notes lived nowhere, and “which jar was the ginger one again?” was a genuine question I asked myself out loud, to a fridge.

So I built a tracker. It’s called HipPotion — same family as everything else I run here. The brewing turned out to be the easy part. Modeling it was where it got interesting.

Why a simple list doesn’t fit

My first instinct was “a batch is a row, log some notes.” That falls apart fast, because kombucha isn’t linear. It has two stages:

  • F1 (first ferment): the big jar of sweet tea + SCOBY, fermenting sour over a week or two. One vessel, one culture.
  • F2 (second ferment): you split that sour base into bottles and flavor each one differently — ginger in this one, blackberry in that one, hibiscus in the next — then seal them to build carbonation.

So one batch becomes many bottles, each with its own flavor, its own carbonation, its own outcome. A flat “batch = row” model can’t express that. And on top of the branching, every jar and bottle produces a stream of observations over time: pH today, Brix tomorrow, “tastes too sweet still” the day after.

That’s three different shapes at once — a lifecycle, a one-to-many split, and a time series — for what looks from the outside like “I made some tea.”

The model I landed on

Six tables, each earning its place:

  • recipes — the templates. Tea blend, sugar ratio, target numbers. A batch points at one.
  • batches — an actual F1 brew, with a lifecycle (planned → active → conditioning → finished) and a reference to its recipe.
  • fermentation_log_entries — the time series. One row per observation per batch: pH, Brix, temperature, taste/smell notes, what I did. This is where the “pay attention and remember” lives.
  • f2_variant_batches — the branch. Each is a flavored bottle split off a parent batch, tracked on its own.
  • starter_log — SCOBY lineage. Cultures have parents; you grow new ones from old ones, and a sick culture ruins a batch, so the lineage matters.
  • botanical_infusions — the flavoring ingredients, managed per recipe.

The shape that took the longest to get right was the F1 → F2 split: a variant has to belong to its parent batch but live its own life. Once that relationship was clean, the whole thing clicked — the app finally matched how brewing actually works instead of how it’s easy to store.

The stack (and where it runs)

Nothing exotic: React + Vite + TypeScript on the front (TanStack Query, shadcn/ui, Tailwind), a Hono + Drizzle ORM API on the back, PostgreSQL underneath. Built with AI coding tools — I leaned on them hard for the React/shadcn front-end, less so for the schema, which I argued out by hand because it’s the part that had to be right.

It runs on my k3s homelab like everything else: a Helm chart deploys the nginx frontend, the Hono API, and a Postgres StatefulSet, all reconciled by Argo CD from Git. Default-deny networking, secrets out of Git — the usual platform defaults. It’s a hobby app, but it gets treated like a real one, because the platform doesn’t know the difference and I don’t want it to.

It became an API for something else

The unexpected payoff: because the data model was clean and the API was just a set of plain REST endpoints, it made a perfect target for an experiment. I later pointed an AI agent at it from n8n — “what’s fermenting right now?”, “log that this batch tastes tart” — and the agent just called the same endpoints the UI does. A good schema is reusable in ways you don’t plan for. The kombucha tracker quietly became a little knowledge base I can talk to.

Honest notes

This is a personal hobby app for an audience of one (me). It’s AI-assisted, it has no tests, and the UI has rough edges. I’m not pretending it’s a product.

But the thing I keep coming back to: the hard, valuable part wasn’t the framework or the deployment — it was sitting with a messy real-world process long enough to find the shape of it. The branching ferment, the time series, the lineage. Get the model honest and the rest is just typing. Get it wrong and no amount of nice UI saves you.

Also, the kombucha’s been better since I started writing things down. Turns out the fridge wasn’t a great database.