A reusable platform layer (MVP) — four workloads today, additive onboarding for the next
A ground-up re-imagining of platform infrastructure — how I'd design one, drawing on 10+ years of engineering. It owns everything two or more apps stand on; each app declares only its own thin slice — a namespace, a Host(...) rule, a database role, and one deploy hook.
A single platform repo is the source of truth for shared infrastructure: the nodes, Kubernetes, Traefik, the wildcard cert, the shared Postgres, and the deploy webhook listener. App repos own their own build + deploy — only what two or more apps stand on lives here.
Host(...) ingress rule (wildcard TLS covers it)Every step is additive; the next app costs a fixed, near-zero amount.
| Axis | Meaning | Applies to |
|---|---|---|
| test-gate | build/tests must pass before deploy | code services |
| release-gate | only a tagged version ships | opt-in |
Trigger and gate are independent axes — each service mixes them to fit its own release cadence.
Host(...) rule to get valid HTTPS.One shared cluster removes per-app cost and ops and forces clean multi-tenancy — namespaces plus a per-app DB role. The tenancy contract scales to more nodes unchanged; the trade-off taken is a single failure domain.
Shared infra used to squat in whichever app repo needed it first → duplication and unclear ownership. Centralizing it adds one coordination point but draws a hard platform/app boundary: apps stay thin and onboarding is self-service.
Build on a node and import into the cluster's containerd (imagePullPolicy: Never, localhost/<app>) — the exact local image is guaranteed to run, and the whole build pipeline is just the cluster itself. A registry can slot in later with the app contract unchanged.
Building on-node makes the image stores the fastest-growing thing on disk, and they used to share a filesystem with the OS, the cluster state and the database — one runaway build could take the cluster down with the disk. Each store now sits on its own volume, so what fills up is that volume and nothing else. A scheduled prune bounds them, with retention split by what each store is for: the runtime store keeps a rollback target, the build store keeps only enough to serve as layer cache for the next build.
One wildcard is the default cert, so apps declare zero TLS config and every new subdomain gets valid TLS automatically — TLS is a platform concern, not an app concern. The trade-off taken is a single cert with a wider blast radius.
A per-app role scoped to its own database gives real tenant isolation on one shared server — a login-only, least-privilege role that keeps each app confined to its own data. Provisioning is additive and idempotent, and enforces the isolation inline as it runs (per-app REVOKE CONNECT / targeted GRANT) — it never touches another app's role.
Each service keeps the trigger that fits it while sharing one signed deploy path. Trigger (how a deploy starts) is separate from gate (test-gate = build must pass; release-gate = tag only) — two independent axes mixed per service.