~/thilagan-digital
← back to logs

Part 7

Custom Dev vs. Off-The-Shelf: Building Proprietary Tools in an OSS World

homelabaiarchitecturetooling

Every homelab eventually hits the same fork: there's an OSS project that does roughly what you need, and it comes with a hundred features you don't, an opinionated data model you didn't choose, and a roadmap you don't control. Sometimes that's still the right call. Sometimes it isn't. Six posts into this series felt like the right point to write down the actual framework, instead of re-deciding it project by project.

Where off-the-shelf wins

The homelab runs plenty of OSS, deliberately: a self-hosted PaaS handles git-triggered deploys and preview environments so that's not a workflow I maintain by hand. Prometheus and Alertmanager do metrics collection and alert routing — solved problems, wide ecosystems, no reason to reinvent either. Caddy fronts every public hostname. Past a certain number of production projects, a self-hosted Supabase stack replaced individually managed cloud projects. In every one of these cases, the problem is a commodity problem — one enough people share that a mature OSS answer already reflects more edge-case handling than I'd write myself.

Where custom wins

Custom code shows up when the tool has to enforce an invariant specific to this system, one no generic tool has an opinion about.

A self-hosted docs search tool exists because the thing I actually needed — searching my own private repos' documentation without paying for a hosted vector database or embedding API — isn't a product, it's a small, specific pipeline: a Next.js/TypeScript frontend, a lightweight Python embedding service, and Postgres with pgvector for storage and similarity search. Ingestion and querying share the exact same embedding step on purpose, so a document's vector and a search query's vector are always comparable — a correctness property that's easy to guarantee in fifty lines of code you own and much harder to verify in someone else's SaaS.

A small config reconciler treats one YAML file as the source of truth for a set of monitor definitions and re-applies it idempotently, rather than letting anyone hand-edit the underlying database rows. The problem it solves is narrow enough that no monitoring platform ships an opinion about it: when a host gets renamed, that's one edit to inventory, and every downstream record moves with it instead of silently drifting out of sync.

monitors.yml  ─▶  reconcile  ─▶  status_module.monitors (idempotent upsert)

Neither of these tools is trying to be a platform. Each one is trying to be exactly as big as the one job it does.

Where Claude and Gemini actually help

Not by deciding what's canonical — that call (what data model, what stays authoritative, what's derived) stays a decision I make. Where they earn their keep is scaffolding fast once that decision is made: CRUD screens for the reconciler's config, the ingestion and chunking scripts for the docs pipeline, the Terraform and Ansible from three posts ago. The architecture is small enough to hold in my head; the boilerplate around it isn't worth my time to type by hand.

The actual question

Every build-vs-buy decision here reduces to one honest question: would I rather maintain fifty lines of code I understand completely, or fight the opinions of a tool I don't control? Commodity problems make that an easy "buy." Anything that has to enforce a rule specific to how my systems fit together makes it an easy "build" — and increasingly, "build" doesn't mean weeks of work anymore, just an afternoon with a scoped brief and an agent that already knows the codebase's conventions.

Which is, six posts later, more or less the whole thesis of this blog: not that AI replaces infrastructure judgment, but that it collapses the distance between having the judgment and having the thing built.