As you may have noticed from what I've been writing lately, I've not been immune to the rise of vibe coding. I use Opencode on a daily basis (using Anthropic models but I'm also increasingly using open source models like Kimi) and I've been building several apps in the past few months. Although many of them are serverless, built on top of Cloudflare's platform, some of them, for a myriad of reasons, are on-prem and live in my homelab.
For these tools, I keep going back to an architecture that is comfortable to me and that gives me the flexibility that I want. It is deliberately minimal. Here is what I keep reaching for:
The Backend: Go
Go is my default. I am not particularly good at it, but at least I'm able to read and write on it and it has a few characteristics that I find really handy for my use cases. To name a few, I like that the standard library covers almost everything I need and that cross-compilation is trivial, which matters to me because I usually deploy on x86 servers and ARM devices (and the occasional MIPS router).
Another small feature that I love is go:embed. If I need to build a web frontend, it is really easy to point Go at the dist folder, and suddenly the entire UI lives inside the binary. There is no nginx to configure, no static file server to run alongside the app, no huuuge node_modules folder to manage on the target host, etc.
The Frontend: React + Kumo UI
I do not enjoy writing CSS, and my attempts at designing UIs from scratch end in sadness. So I do not try. I use React because AI models write it reliably, and I use Kumo UI because it gives me components that look intentional without requiring intention on my part.
I try to instruct my LLMs to keep the frontend aggressively simple, just fetch calls to the backend, state managed with hooks, and some basic routing.
The Database: SQLite
Oh boy, I'm in love with SQLite. For a personal tool, a homelab service, or even a enterprise grade app, it is more than capable. Everything living in a single file, with no ports to open or separate containers or processes to monitor.
Backup was a problem in the past, but thanks to Litestream now I'm able to back it up easily. For those who don't know Litestream, it is just a binary from It streams the WAL to an S3-compatible bucket in real time. If the server dies, I restore from the last snapshot. The disaster recovery story for my fridge tracker is, absurdly, easier and faster than some production systems I have worked on.
The Configuration: One YAML File
I used to rely on a lot of CLI flags and env variables, but lately I've been using just a single CLI flag pointing to a config.yaml. Server port, database path, API keys, expiration thresholds, whatever the app needs is defined there. Another big benefit of this approach is that if the tool has a web dashboard, I can add settings page and easily update the config file directly from the service itself.
Deployment: Pick Your Poison
Because everything compiles to a single binary, I have two options, and both are simple:
- The container route: I usually add a Dockerfile to build a tiny scratch image, mount the config.yaml and the SQLite file as volumes and run it.
- The bare metal route: Cross-compile for the target, scp the binary over, run it under systemd or whatever the host has. I have done this on routers mainly and it just works.
It's about keeping control
It is completely fine to use AI. It is great to build tools faster. But there is a dangerous trap here. I'm neither a programmer nor a SRE, if I let the AI spin up a distributed Postgres setup, a microservices architecture, and a complex deployment pipeline, two things are going to happen: I won’t be able to maintain my services in the long run, and I won’t even be able to read and fix it when things go sideways. I won’t actually own the code. I will just be a hostage to it.
So, for me, it is really important to instruct the AI to write maintainable code and to carefully oversee the architectural decisions being made. I need to make sure I can live with them in the future. That way, the AI writes most of the code, but I stay in the driver's seat.