The box and the loop - how I build small products (almost) on autopilot
In the March post I said I’d talk about prod systems and AI “later”. This is later.
So this is less a “how-to” and more a diary entry. It’s how I currently think about building small products - mostly by myself, mostly with an agent, and increasingly while I’m not even looking at the screen. Snapshot of thoughts, might be outdated in a week, you know the drill.
There are two halves to it: the box I built, and the loop that runs on top of it.
The box
I have a small cluster of three Hetzner servers. Nothing fancy, the cheap ones.
- 1 manager - the brain. Also hosts a few little apps that watch everything else and shout at me on Slack when something’s off.
- 2 workers - one is my dev environment, the other is prod.
That’s it. One box to release to and poke at, one box that real people (well, my dad - hi dad) actually hit.
I’m running Docker Swarm to glue it together. And yes, I know, k8s is right there. But Swarm is the alternative I can actually hold in my head. Overlay networking, services, constraints to pin a workload to the right node - that’s about the whole mental model, and it’s small enough that when something breaks I understand why. For a three-node hobby cluster, “I understand why” beats “industry standard” every single time.
Honestly the best part was just learning how the networking fits together. How services find each other, how an overlay network actually works, how a request lands on the right node. That stuff clicked in a way it never did when I was clicking around a managed k8s dashboard.
And I don’t drive Swarm raw - I run it through Dokploy. That’s the important bit I keep forgetting to mention. Dokploy sits on top and gives me the boring-but-essential glue: a UI over the cluster, per-app environments, TLS, and - the part that matters most for what comes next - a clean deploy trigger I can hit from CI. So Swarm is the engine, Dokploy is the dashboard and the ignition. I get to keep the small mental model and not hand-write a rollout every time.
The loop
Here’s the part I’m actually excited about.
The whole CI lives in GitHub, and deployments are driven by labels. I slap a label on a PR and it goes to dev. That one decision - “releases are just a label” - is what makes the rest possible, because a label is something an agent can apply too.
My default rhythm now looks like this:
- I sit with an agent for a while and dump everything I want into a backlog. Half-baked, doesn’t matter, just get it out.
- I use Linear for this, mostly because it integrates cleanly with basically every agent harness I throw at it.
- Then I go item by item and discuss each one with an agent - digging into the actual detail, the gotchas, what “done” means. (This is the specs-and-loops workflow from the last post, just pointed at real product work.)
- When an item is well-specified, it gets a
ready for agentlabel. - Then I spin up an agent, it picks up a
ready for agentitem, does the work, and opens a PR.
And now the fun branch:
- If the change is safe - no DB migration, nothing scary - GitHub and Dokploy release it to dev and wait for the deployment to finish. Then the agent separately opens the deployed app and tests the changed path there. On real infra. While I’m doing something else.
- If it’s not safe, or I want a closer look, it tests locally first.
This became my go-to way of working while building Offer Link. It stopped being a “let me try this AI thing” experiment and just quietly became… how I build.
Local, without the port wars
Testing locally sounds trivial until you’re running four docker compose stacks at once and every single one wants port 3000.
So locally all browser-facing traffic goes through one shared Caddy gateway. Each Compose project gets a name, and that gives the app a stable URL - offerlink-home.localhost instead of a port I have to remember. Inside the stack, services still find each other through Docker’s own DNS. Multiple stacks up at the same time, zero port wars, and the agent reasons about “the app” by a stable name instead of guessing which port it grabbed. Small piece of plumbing, disproportionate payoff - enough that I gave it its own post.
Showing me the work
Two things make the AFK part actually trustable.
Playwright reports. The agent can drive the app and hand me back a report with screenshots. So “it works” isn’t a claim, it’s evidence I can glance at.
The gallery. The capture part is already real: the agent drives the app and screenshots each meaningful state. What I couldn’t find was the bigger gallery wrapper I remembered. So the next tiny layer is obvious - generate one HTML index over those shots, then extend it to compare UI concepts side by side like a lookbook. I wrote down what exists and exactly what still needs building.
Why it’s cool
Put it together and the shape is: a box small enough to understand, a loop safe enough to leave running, and enough feedback (Playwright reports, screenshot runs, Slack alerts) that “autonomous” doesn’t mean “blind”.
I dump ideas into a backlog → sharpen them with an agent → label them → an agent ships the safe ones to dev and shows me → I review the report and screenshots instead of babysitting a terminal.
It’s not a critical prod system. It’s the thing I use to build critical prod systems, cheaply, mostly alone, and increasingly while I’m off doing something else. And after the skills and ralph repos, this cluster is starting to feel like part of the same “new dotfiles” idea - the personal substrate you carry between projects.
Anyway. That’s the box and the loop right now. I already pulled two corners of it into their own posts - the local gateway and the screenshot galleries - and there’s more where that came from. Ping me if you want to dig into any of it.