A one-person platform team
TL;DR
- Context: B2B2C automotive SaaS, lean and bootstrapped. Tech team: 1 product dev and me, owning 100% of the platform and the infrastructure.
- Scale: 60+ tenants in production (≈200 containers), two data centers in hot-standby, four-nines target.
- The thesis: one person runs this not through heroics but through architecture: everything declarative, everything observable, everything with a way back.
The context: when the platform team is you
In a bootstrapped SaaS there is no "open a ticket with infra". I am the infra, and also the SRE, the DBA, the security team and the on-call rotation. That changes the calculus of every technical decision: every component that enters the architecture is a component I will patch, monitor, and get woken up for.
The discipline that emerges from this has a name: minimal surface area. It is not aesthetic minimalism. It is survival. The sections below are the same decision applied at different layers.
The shape of the problem: 60+ tenants, ≈200 containers
Each tenant (a dealership with its own domain) is materialized as its own set of containers (site, API, backoffice), all generated from the same versioned template, on top of a shared data plane (PostgreSQL + MongoDB) with per-tenant isolation in the application layer. Routing is by domain, at the reverse proxy, and DNS for the entire fleet is automatically reconciled against an anchor record: a new tenant goes into a manifest, and the rest just happens.
The trade-off is deliberate: per-tenant containers cost more memory than a pure multi-tenant monolith, but they buy failure isolation (one tenant going haywire doesn't take down its neighbors), deploy and rollback per tenant, a fleet a single human can reason about. And simplicity at the edge: each dealership uses its own commercial domain pointing straight at its container set, with no complex proxy-and-rewrite layer multiplexing a single app across dozens of third-party domains.
High availability that doesn't depend on me being awake
Production runs in two geo-separated data centers in hot-standby: PostgreSQL with Patroni (auto-failover), MongoDB replica sets with an arbiter at a neutral third site, Redis Sentinel, asynchronous replicas with an RPO of seconds. In front, a load balancer whose health check only returns 200 in the data center where the database is the writable leader: traffic follows the database's failover on its own, with detection in ~1–2 minutes, no human intervention.
The full story of how production got to these two DCs, with zero downtime, is the migration case. Here, the point is a different one: automatic failover is what lets a team of one sleep.
The internal platform: GitOps without ceremony
Fleet deploys are governed by GitOps, via a lightweight control plane, an open-source tool adopted on purpose (central manager + per-VM agents, zero-trust network). Minimal surface area applies here too: I don't reinvent commodity; the bespoke engineering goes where off-the-shelf tools don't reach (the manifest that materializes the fleet, the DNS reconciler, the failover pre-warmer, the bespoke observability):
- Desired state in git. Deploy is commit + push; the control plane only applies what has a new commit. Drift detection compares what is running against what git says should be running.
- Adoption without recreation: existing containers can be adopted into the declared state with no downtime.
- The control plane doesn't manage itself. Explicit guard: the pieces that hold it up (the control plane itself, the edge) only come up via direct access, because the tool that solves your problem can't depend on itself to get fixed.
- Secrets out of git: self-hosted manager, with least-privilege machine identities per VM.
Before this there was a point-and-click container management UI. I replaced it because a UI has no diff, no history, no revert. Git gives you all three for free.
CI on GitHub Actions (multi-arch builds), images in a private registry, and the tenant fleet provisioned from a manifest + versioned scripts on a timer: a new tenant stops being a procedure and becomes an entry in a file.
Why NOT Kubernetes (yet)
The question everyone asks. The short answer: Kubernetes solves organization-scale problems I don't have, at an operating cost I would pay alone.
The long version: what I need from an orchestrator is desired state, reconcile, drift detection, rollback and routing. And the GitOps control plane + Docker + reverse proxy deliver that with a fraction of the moving parts. The Kubernetes control plane is, itself, a distributed system someone has to operate, upgrade and understand at 3 a.m. Here, that someone has a first and last name. My fleet is stable and predictable (tenants have no Black Friday elasticity), so dynamic scheduling and autoscaling (the heart of K8s's value) would sit idle.
And the genuinely hard problems in this operation (cross-DC database failover, replication, DNS/LB cutover, immutable backup) live below and above the orchestrator. Kubernetes wouldn't solve any of them for me; I would just end up operating them and Kubernetes.
The "(yet)" is honest: at larger scale, or with a team, the math changes. Knowing exactly which problem K8s would solve for me (because I solved each one by hand) is precisely what lets me know when to adopt it.
By the same principle, no Terraform and no Ansible: VMs are born from a cloud-init template + versioned scripts in the repo, straight against the hypervisor's API. For 2 hypervisors and a dozen VMs, an extra declarative layer is more surface to maintain. The decision, recorded in an ADR, was "one way to manage things, less surface". IaC is the principle; the tool gets chosen by the size of the problem.
Observability: the eyes that replace a team
Here it was the inverse of the control plane: built bespoke, because generic observability doesn't know what a tenant is. If one person runs the fleet, the fleet has to report on itself:
- Metrics across the whole fleet (hosts, containers, Postgres/Patroni, Mongo, Redis, connection pooling), with months of retention and ~100k active series.
- 180+ external probes hitting the tenants' sites from the outside: the availability that gets measured is the one the customer sees, not the one the server believes.
- Structured logs from the entire fleet in a central pipeline: millions of lines/day with severity classification.
- 30+ alert rules routed by criticality (critical wakes me up; the rest can wait for coffee) and crossed deadman switches: the system that monitors is monitored by another. Who watches the watcher is a question with a written answer.
And the probe list generates itself from the tenant manifest: a new tenant enters monitoring without anyone remembering to add it. Automation that depends on human memory is not automation.
Layered backups (because a replica is not a backup)
An asynchronous replica protects against hardware loss. And it faithfully replicates your bad
DELETE. Hence, independent layers:
- Live replica cross-DC: RPO of seconds, for loss of a site.
- ZFS snapshots every 15 minutes: for the "oops" from a moment ago, instant rollback.
- Daily encrypted backup of the entire VMs, deduplicated, with restores tested and timed (hundreds of GB restored in ~20 minutes).
- Off-site to two independent destinations, another data center and another provider, the latter with immutable object-lock: not even ransomware holding my credentials can delete it. And the revenue database gets a logical dump every hour: catastrophe RPO dropped from 24h to 1h.
The bill at the end of the month
All of this (two data centers, real HA, observability, immutable backups) runs on two bare-metal servers and a handful of cheap managed services, at a fraction of what the same footprint would cost in managed cloud. Hot-standby instead of active-active was a deliberate cost decision: half the hardware sitting "idle" is the insurance premium, and there is still headroom for the fleet to double.
Cost optimization here is not dashboard FinOps: it's architecture that fits the business.
Lessons
- Minimal surface area is a strategy, not an aesthetic. Every component you don't have is one less patch, one less alert and one less failure mode. Pick boring technology and operate it well.
- Automation as survival. A team of one doesn't scale through effort; it scales by not needing to be present. If a process depends on human memory, it has already failed; it just hasn't told you yet. (The corollary: when 200 containers decided to restart at the same time after a reboot and drowned the databases, the answer was not "restart more carefully next time" but a staggered, health-check-gated bring-up, at boot, written once.)
- Full ownership teaches what abstraction hides. Solving failover, replication and DR by hand is what tells me exactly what I would be buying from an orchestrator or a cloud, and when it's worth buying.
- Treat the lab like production and production like code. The operating pattern that runs the SaaS (git as the source of truth, lightweight agents, zero-trust) was proven first in my homelab: 12 years of lab became method, not hobby.
Other case studies
Migrating 50+ live tenants across data centers, with zero downtime Hot replica, atomic cutover, and proof by packet capture, not optimism. 12 years of homelab: my self-hosted AI stack Local LLMs on a GPU, hybrid RAG, and an agent that pentests my own SaaS.Always happy to compare notes with people who run real infrastructure.
LinkedIn