Skip to main content

Seven Months to Self-Host an AI Stack That Was Supposed to Take a Weekend: What OpenMake's Post-Mortem Bills Against a PaaS

12 min readDora NodaDora Noda
Share
On this page

"I thought self-hosting our AI would take a weekend. It took seven months." That is the slug on OpenMake's September 5 retrospective — seven months, roughly 2,100 commits, and four bugs the author pins to the public changelog so you can check his work. It is the rare self-hosting post-mortem with receipts instead of vibes, which makes it the perfect specimen for the question every small team eventually asks: what does "just Docker and a box" actually cost, in engineering time, versus pushing to a PaaS?

Here is the bottom line up front, worked honestly below: a year of VPS savings evaporates after about two days of engineering time, and most of OpenMake's seven months was building product, not fighting ops. The self-hosted path still wins where it always wins — no meter, data stays home — but the missing orchestration layer keeps billing forever in upgrades, backups, and the second machine. All three paths, Year 1, for the same small-team AI-stack shape:

Owned box (OpenMake's shape)VPS + DockerPush-to-deploy PaaS
Infra, Year 1~$1,900 (capex amortized)~$330~$960
Eng time, Year 1 (at $50/hr)~$2,750 (~55h)~$2,750 (~55h)~$375 (~7h)
Year 1 total~$4,650~$3,080~$1,335

At a loaded rate of $150/hr the totals become roughly $10,150 / $8,580 / $2,085 — the PaaS wins bigger the more your time costs. Everything below is the evidence for those rows, plus the honest part most comparisons skip: what the seven months actually were.

What the seven months actually were (and where the "weekend" came from)

First, source the expectation. The "weekend" is the author's own framing — it is in the post's URL slug — and it comes from a setup sentence that reads like a weekend project: "Self-hosting needs Node 24, PostgreSQL, and Docker. Setup time is mostly however long your model endpoint takes." A Mac mini runs the API and web under PM2, with PostgreSQL, Redis, and the sandboxed agent, MCP, and artifact processes in Docker; an NVIDIA DGX Spark GB10 next to it runs vLLM plus embeddings and image generation, joined over a private Tailscale link. That genuinely is a weekend to assemble. The weekly dev log starts the week of January 19, 2026, the repo was created February 3, and the September post sits at roughly 2,100 commits under MIT.

Now the audit that keeps this post honest: the four changelog bugs are almost entirely application bugs, not self-hosting bugs.

  • Bug 1 — Cloudflare's 100 MB edge limit rejecting uploads with HTTP 413 before they reached the API. A request-path bug fixed with a chunked upload protocol; a PaaS deploy behind Cloudflare hits the identical wall.
  • Bug 2 — a static catalog line pinning the app to a model name that no longer existed after the DGX swapped models. A config bug fixed by discovering models from LiteLLM's /model/info.
  • Bug 3 — fan-out research calls eating 429s (5-for-5 on one free key, 3-for-5 on another). A client-resilience bug fixed with per-provider semaphores and Retry-After backoff in 1.40.0.
  • Bug 4 — context-window overflow handling with truncation, then a 413 with an audit record and webhook alert. Pure application logic.

The September 2 security review batch — stored prompt injection via overwritable system skills, an IDOR letting push subscriptions register under another user's id, CSV formula injection, an existence oracle on MCP server status — is appsec, and no hosting model fixes an IDOR for you.

So the correct reading is not "self-hosting cost seven months." It is: building the product cost seven months, and the self-hosting tax is the ops tail wrapped around it — PM2 process management, Docker upkeep, Postgres care and feeding, Tailscale networking, and every future upgrade hand-rolled on a box the team owns. One omitted week in the dev-log archive (W18, zero commits) is a nice tell about the pace: this was product work with ops interleaved, not an ops death march. Any comparison that bills all seven months to Docker is lying; any comparison that bills zero is lying too. The table above bills only the ops tail.

The worked bill: what "just Docker and a box" actually costs

Model a typical small team running this shape for a year. Assumptions are stated so you can re-run them: infra at September 2026 list prices, eng time at a conservative $50/hr with sensitivity at $150/hr, and ops-hour estimates for a team that already knows Docker (a team learning it pays more).

The infra rows. OpenMake's owned hardware is a Mac mini (roughly the $600–1,000 class) plus a DGX Spark that lists at $4,699 after February's memory-shortage hike — call it ~$5,500 capex, ~$1,830/yr amortized over three years, plus power. The VPS variant is an 8-vCPU/16 GB box at $19/mo post-repricing ($230/yr) with ~$100/yr of volumes and snapshots, since anyone pricing Hetzner off a 2025 blog post is two repricings stale. The PaaS column is Render-shape pricing at list: a $25/mo Standard service plus a worker, $20/mo managed Postgres, $10/mo Key Value — roughly $80/mo, ~$960/yr.

The engineering rows — this is where the bill actually lives:

Ops line, per yearDocker + box (either kind)Push-to-deploy PaaS
Initial setup (OS hardening, Docker, Postgres, TLS, networking, backups)16–24h2–4h (connect repo, provision PG)
Upgrades (Node minors, Postgres patches, Docker, OS)~12h (3h/quarter)~0h
Backups + restore testing~12h (build once, test restores)~0h (PITR included)
Incident response (disk, OOM, certs, edge quirks)~12h~4h
Scaling past one machine0h until the cliff, then 20–40h~1h (a slider)
Steady-state total~50–60h/yr~5–10h/yr

At $50/hr, ~55 self-hosted hours cost ~$2,750 against ~$375 on the PaaS — a ~$2,375/yr ops gap that dwarfs the $630/yr infra savings of the VPS over the PaaS. Divide one by the other and you get the sentence that matters: roughly 13 hours of engineering time — under two working days — erases a full year of VPS-vs-PaaS infra savings. At $150/hr it takes about four hours. Sensitivity cuts the other way too: a team already fluent in this stack, with scripts from the last project, might halve the ops tail — and a team learning Postgres major upgrades the hard way (the classic pg_dumpall dump-and-restore dance across a Docker Compose version bump) can double it in one bad weekend.

Note what is not in either column: the product work. The chunked-upload protocol, the LiteLLM discovery fix, the 429 backoff, the overflow handler, the 1.39.0 security batch — both columns pay those identically. That is the discipline that makes the comparison real instead of tribal.

Where the self-hosted path wins

With the bill modeled, give the box its due — three wins that are structural, not sentimental.

No meter. The DGX Spark serves the default local model (a 27B-class Qwen through vLLM with a 262K context window) at a marginal cost of roughly zero per token. Agentic workloads — multi-turn sandbox runs, fan-out deep research, mixture-of-agents discussion mode — are precisely the usage shape that meters punish most, because token burn scales with autonomy, not with users. The retrospective's Bug 3 is the meter's shadow even in miniature: free-tier external keys rate-limiting real fan-out. On owned iron there is no per-seat, per-token, or per-run line item, and the amortized hardware gets cheaper per unit of work the harder you push it.

Data stays home. Register no external key and every model call stays local; BYOK keys are encrypted at rest with AES-256-GCM. For a team whose prompts contain customer data, source code, or anything regulated, "the prompt never left the building" is not a vibe — it is an audit answer. No PaaS surcharge buys you that property; it comes only from owning the inference path.

Fixed cost amortizes with heavy use. The PaaS column scales with services, seats, and add-ons; the box costs the same whether it serves ten agent runs a day or ten thousand. Past a certain utilization the lines cross and never cross back — which is exactly why the honest comparison prices your utilization instead of quoting a rule of thumb.

Where the missing orchestration layer keeps billing

Now the other side — the four recurring charges no retrospective ever lists because they arrive as Tuesdays, not incidents.

Upgrades are hand-rolled forever. Node 24 minors, Postgres patches, Docker engine, the host OS, PM2 itself: each is a small maintenance window the team owns end to end, with rollback being whatever they built. Postgres major versions are the sharp edge — containerized Postgres does not containerize the upgrade, and the dump/restore (or pg_upgrade) procedure across a Compose bump is the closest thing this stack has to a scheduled outage. A managed Postgres turns all of this into someone else's changelog.

Backups exist only if someone built and tested them. pg_dump to a cron job is thirty minutes; verified point-in-time recovery with tested restores is a project, and untested backups are backup theater. The PaaS column's PITR is not a feature advantage — it is a discipline the box requires you to supply yourself, every quarter, forever.

Scaling is a cliff, not a slope. The retrospective states the limit plainly: "No Kubernetes story, and none planned. Single host by design." One host is a fine architecture until the day it is not — the model host is saturated, the app host needs maintenance, a second GPU box arrives — and then every assumption (local volumes, PM2 process lists, Tailscale point-to-point, deploys as SSH-and-pull) gets renegotiated at once. That 20–40h cell in the table is the cheapest honest estimate for crossing that seam; "we'll figure it out when we grow" is how it becomes 80.

The edge path still lies to you. Bug 1's lesson — "your app's configured limit is a claim about your app, not about the path a request actually takes" — generalizes to every layer the team operates: Cloudflare limits, Tailscale throughput, Docker bridge networking, disk I/O on a mini. Each layer is one more path whose limits you discover in production.

The honest Cluster-API read

This is the section the TODO spec demands, so here it is without hedging: Kubernetes would not have shortened those seven months — but a PaaS-shaped deploy surface on top would have.

Walk the changelog through it. Would Kubernetes have prevented the Cloudflare 413? No — the edge is upstream of the cluster. The static model catalog? No — that is app config. The 429 fan-out? No — client resilience. Context overflow? No. The IDOR batch? No. What Kubernetes would have added is a control plane to operate — etcd, API-server upgrades, CNI, CSI, node lifecycle — on a fleet of two machines where the management overhead has nothing to amortize across. For a single-host AI workspace, K8s is pure fixed cost against zero of the observed bugs. The retrospective's "no Kubernetes story, and none planned" is not a gap in this context; it is correct sizing.

But notice what a PaaS-shaped surface would have absorbed: push-to-deploy replacing PM2 hand-rolls and SSH deploys; managed Postgres with PITR deleting the backup project; preview environments for the web UI; a second-machine story (even just "add a node, the scheduler notices") converting the scaling cliff into a slope; TLS, health checks, and restarts as platform behavior instead of composed tooling. None of that requires the team to learn Kubernetes — it requires someone to have productized the toil layer above it. That is the actual gap the seven months reveal: not "we should have run K8s," but "every hour we spent being our own PaaS was an hour not spent on the product, and the PaaS hours repeat yearly while the product hours compound."

For a Cluster-API fleet operator, that is the whole thesis in one sentence: the value of the fleet is not the cluster — it is the deploy surface the cluster makes possible. CAPI plus a PaaS layer gives you OpenMake's wins (owned hardware, no meter, data home) while deleting most of the ~55h/yr ops tail. CAPI without the surface just moves the hand-rolled layer somewhere more declarative.

What to do Monday morning

Four decision rules, in the order you need them:

  1. Split the stack by meter, not by hype. If your inference is heavy, own the GPU box — the meter math is overwhelming. Put the stateless app tier on a push-to-deploy surface anyway; PM2 on a mini is not where your differentiation lives.
  2. Budget the ops tail explicitly before comparing infra prices. Write down 50 hours a year at your loaded rate as the Docker-and-a-box floor. If the infra savings do not survive that line, they were never savings.
  3. Price the second machine before you need it. "Single host by design" is a complete sentence only with a costed next step attached. If crossing to two machines means re-architecting storage, deploys, and networking simultaneously, that is a cliff with a date, not a plan.
  4. Do not adopt Kubernetes to shorten product work. Adopt it — if at all — to host a PaaS surface that deletes toil. If no surface is planned, K8s on two machines is the most expensive way to learn what the retrospective already states plainly.

The deepest lesson of the post-mortem is not about hosting at all. It is in the author's closing ethic: "a changelog that only contains features is not a changelog." Seven months, four checkable bugs, one missing week left honestly empty, security findings published rather than buried. Whichever side of the bill you land on, that is the practice worth copying — because the teams that can point at their changelog can also point at their ops tail, and only those teams are choosing their bill on purpose.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Own the hardware, skip the 55-hour ops tail: managed Postgres with PITR, push-to-deploy, and a second-machine story that is a slider instead of a rewrite. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex