← Devlog
devonboardarchitecturellmpython

Why DevOnboard is built the way it is

The architecture decisions behind DevOnboard — and the constraints that made them, from privacy-first LLM abstraction to skipping Redis.

DevOnboard is a self-hosted tool that scans a GitHub repo, interviews you about the gaps a scanner can’t see, and generates a structured onboarding guide for new developers. Before any of it existed, the architecture was written down as a set of decision records. This is the reasoning behind the big ones.

Three constraints drove everything: I’m one developer, so every added moving part is maintenance I personally carry. It’s both a portfolio piece and a potential product, so it has to demo well and hold up as something a company could actually run. And the target customer is privacy-conscious — the pitch is “your code never leaves your infrastructure,” which quietly shapes more decisions than any framework preference.

Python + FastAPI on the backend. My professional stack is C# and TypeScript, but the heart of this app is an LLM pipeline, and Python is where that ecosystem lives. FastAPI gives async request handling (this app spends its life waiting on GitHub and LLM APIs), Pydantic validation at every boundary, and a clean typed API layer. SQLAlchemy async + Alembic pair naturally with it.

Scanning via the GitHub REST API — no cloning. The obvious design is to clone the repo server-side and walk the tree. DevOnboard instead fetches known-path files (package manifests, Docker configs, CI definitions, .env.example) by blob SHA over the REST API. No customer source code is ever stored on the server, which is both the privacy story and a lot less infrastructure.

The LLM sits behind a Protocol. Every model call goes through an LLMService interface with two methods — generate_structured and generate_text — and the provider is a config value. MVP uses the Claude API because it’s production-grade and fast to integrate. The production path for security-conscious customers is Ollama running locally in the same Compose stack, so repo data never leaves their network. The abstraction costs almost nothing now and makes that swap a config change, not a rewrite. Structured outputs are validated with Pydantic at the boundary — raw model output is never trusted as data.

PostgreSQL for everything, including refresh tokens. The reflexive choice for session tokens is Redis. But this is a single-tenant, self-hosted tool with maybe a dozen concurrent users — a fifth container to babysit buys nothing. Postgres handles the schema, the scan results (JSON), and the token table without breaking a sweat.

Docker Compose, single-tenant. One company per install, docker compose up from a clean clone. No multi-tenancy, no companies table — a single-row settings table instead. Self-hosting is the product’s trust model, so the deployment story stays boring on purpose.

GitHub OAuth as the only auth. If you can see the repo on GitHub, you can see its onboarding doc. That one rule eliminates invite links, seat management, and a whole class of access-control code — GitHub already maintains the permission model better than I would.

The common thread: every decision either removes a moving part or protects the privacy story. The interesting engineering budget goes where it pays — the scanner and the prompt pipeline — not into infrastructure the product doesn’t need.