Back to work

Multi‑tenant SaaS • Next.js • Terraform • AWS

Orbit Platform

Four businesses launched on it. They outgrew it, so I took it apart.

Client
Orbit (my studio)
Role
Solo build — architecture, infrastructure, and delivery
When
Mar 2026 – Aug 2026
Next.js 15TypeScriptDynamoDBStripeTerraformTurborepo

The problem

Four small businesses needed booking sites. Building each one from scratch meant repeating the same auth, payments, scheduling, and deploy pipeline four times, and maintaining four copies of every bug fix forever.

Constraints

  • —Each client needed their own domain and their own AWS account — no shared blast radius
  • —One engineer, so operations had to be automated or they would not happen
  • —Clients pay small-business prices, so per-tenant running cost had to stay low

What I built

A pnpm/Turborepo monorepo with shared packages for booking, payments, and auth, plus per-client Terraform stacks that stood up an isolated AWS account, Route53 domain, ACM certificate, and deploy pipeline. Stripe Connect handled payments per tenant. 244 commits over five months.

Decisions I'd defend

One AWS account per client instead of one account with tenant IDs

A bug in tenant isolation is unrecoverable — it leaks one client's customer data to another. Account boundaries make that failure mode structurally impossible instead of a code review away. It cost more in setup and Terraform complexity, and I would make the same call again.

Extract every tenant and freeze the platform

Four businesses using the software in earnest started needing different things — which is what success looks like, and what a shared abstraction is worst at. Packages grew conditionals until a change for one client meant regression-testing four, and the monorepo was charging me coordination cost for reuse I was no longer getting. Holding them together would have slowed all four to protect a structure that had already done its job. In August I pulled every app into its own repo and tagged the platform v0-archive.

What changed

Four live businesses — a DJ company, a mobile detailer, a golf course, and an event venue — each now on its own repo, its own infrastructure, and shipping independently. Nothing went dark in the move, and I still run all four through Orbit; what was retired was the shared platform underneath, not the client relationships. The patterns it proved came with them: account-per-tenant isolation, and deriving every AWS resource name from a single prefix so environments move atomically, are both running in the standalone apps today. The platform was scaffolding that worked — four buildings are standing, and the scaffolding came down.

What I'd do differently

Building the platform first was the right call for getting four businesses live with one engineer, and the wrong thing to hold onto once they diverged. Starting again, I would build three sites plainly and factor out only what demonstrably repeated, rather than designing for reuse I had not yet observed — the shared feature packages are exactly where the conditionals piled up. Account-per-tenant isolation is the piece I would do identically. That is the thinking behind Concours: pick one vertical, learn its shape from real operators, then abstract.