Every cloud provider is, at its core, a pile of software standing between you and a hypervisor. The dashboard, the API, the billing page — all of it eventually calls something that says “make me a VM.” Which means you can build one. Not a datacenter — the software. A control plane that turns one virtualization box into something that looks and feels like a real cloud: dashboard, REST API, provisioning, custom domains with TLS, resize, rebuild, the works.

This post is the anatomy of that build. Every piece here runs fine on a single Proxmox host and a $5 VPS. None of it requires anything exotic.

Control Plane vs. Data Plane

The first design decision is the one every real cloud made long ago: separate the control plane (the thing that decides what should exist) from the data plane (the thing that runs workloads). The control plane is a small web app — a Python API server, a Postgres database, a background worker, and a reconciler loop. It does not run tenant workloads. It holds desired state: “account X should have a 2-core VM named Y running image Z.”

The worker turns desired state into reality by calling the hypervisor’s API. The reconciler runs on a timer, compares desired state to actual state, and heals drift — a VM that should be running but isn’t gets started, a VM that was deleted from the database but still exists gets flagged. This split sounds like over-engineering for a homelab until the first time provisioning fails halfway through. With a reconciler, a half-created VM is just drift that gets cleaned up on the next pass. Without one, it’s a support ticket you file with yourself.

Provisioning: cloud-init Templates All the Way Down

Proxmox’s API is quietly excellent and token-based. Create scoped API tokens — one for the control plane with only the permissions it needs (clone, configure, start/stop within one resource pool), never the root credential. If the control plane gets popped, the blast radius is the tenant pool, not the hypervisor.

“Images” are just pre-built cloud-init templates: a base distro image, a web-app image with a deploy agent baked in, game-server images with LinuxGSM preinstalled. Creating a server is a linked clone off the template (ZFS makes this near-instant), a cloud-init drive with the user’s SSH keys and hostname, and a start call. Resize is a config change plus a filesystem grow. Rebuild is destroy-and-reclone while keeping the IP and keys — which is exactly how the big providers do it, and why rebuild wipes your disk there too.

Tenant Isolation: Assume Every VM Is Hostile

The moment a VM can be created by anyone who isn’t you, treat it as hostile. Tenant VMs live on a dedicated VLAN with its own subnet. The firewall is the only gateway, and the rules are one-directional: tenants can reach the internet, tenants cannot reach the management network, the hypervisor, or anything else in the lab. The control plane reaches into the tenant network when it must; nothing reaches out of it. Test this with an actual VM and an actual port scan, not by reading your own firewall rules and nodding.

The Edge: A $5 VPS Is Your Datacenter Ingress

If your hypervisor sits behind a residential connection or CGNAT, you don’t expose it. You rent the cheapest VPS any provider sells and make that your public face. A WireGuard tunnel runs from the VPS back to your firewall, and the VPS runs a reverse proxy — Caddy is the right tool here — that forwards traffic over the tunnel.

The trick that makes custom domains work is Caddy’s on-demand TLS: certificates are issued at handshake time for any domain that shows up. Left open, that’s an abuse vector — so Caddy’s ask endpoint points at the control plane, which answers “yes, this domain belongs to a tenant” or “no, refuse” before any certificate is issued. A user CNAMEs their domain at the edge, the control plane validates ownership, and TLS just happens. That’s the entire custom-domains feature of a modern PaaS in about thirty lines of config.

Push-to-Deploy Is a Git Hook

The “deploy on git push” magic every PaaS sells is a bare repo, a post-receive hook, and a build script. The web-app image ships with a small agent: push to the VM’s git remote, the hook checks out the code, builds it, and restarts the service. No container registry, no CI pipeline, no YAML. It is genuinely 1990s technology wearing a 2020s feature name, and users love it anyway.

The Interesting Part: Make It Agent-Native

Here’s the piece that has no established playbook yet. Put an MCP server in front of the control plane API — with real OAuth 2.1: dynamic client registration, PKCE, short-lived tokens scoped to the account. Now an AI agent can be handed the endpoint and legitimately operate the cloud: list plans and images, provision a server, wait for it to boot, add an SSH key, deploy something, resize it later.

Watching an agent go from “spin me up a Factorio server” to a running, reachable game server with no human in the loop is the moment the whole build justifies itself. The tools are thin wrappers over the same REST API the dashboard uses — which is the old lesson, rediscovered: if the API is the product, every new client (human, script, or model) is cheap. Design the API first and the dashboard, the CLI, and the MCP server are all just skins.

What This Doesn’t Get You

Honesty section. A single hypervisor is a single point of failure: no live migration target, no SLA, and every power blip is a full outage. A residential uplink has asymmetric bandwidth and a consumer ISP’s terms of service. There’s no DDoS protection a $5 VPS can meaningfully provide, and no abuse desk. This architecture is a phenomenal way to run infrastructure for yourself, your friends, and your communities — and a terrible thing to charge strangers money for. Know which one you’re building before you wire up a payment form.

But as a pattern? One box, one cheap VPS, one weekend of focused work, and you have a private cloud with an API your tools — and your agents — can drive. The providers were never magic. They were just first.