<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cdn on Raj Singh</title><link>http://rajsingh.info/tags/cdn/</link><description>Recent content in Cdn on Raj Singh</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 18 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://rajsingh.info/tags/cdn/index.xml" rel="self" type="application/rss+xml"/><item><title>Self-Hosting an AI CDN</title><link>http://rajsingh.info/p/self-hosting-an-ai-cdn/</link><pubDate>Fri, 18 Sep 2026 00:00:00 +0000</pubDate><guid>http://rajsingh.info/p/self-hosting-an-ai-cdn/</guid><description>&lt;img src="http://rajsingh.info/p/self-hosting-an-ai-cdn/cover.png" alt="Featured image of post Self-Hosting an AI CDN" />&lt;p>A couple years ago I wrote about my &lt;a class="link" href="http://rajsingh.info/p/cluster-framework/" >homelab cluster framework&lt;/a>. How I ran Kubernetes at home. What I picked. Why. That was a lab.&lt;/p>
&lt;p>The interesting problem is no longer &amp;ldquo;do I have a cluster.&amp;rdquo; It is &lt;strong>where does this work run.&lt;/strong> An agent opening a pull request, a model answering, a database keeping history, a camera in the house — those are not the same environment. If you put them all in one place you do not have a company. You have a server with opinions.&lt;/p>
&lt;p>I ended up with three environments that together are a scheduling control plane. Not a scheduler in the kube sense. A place you can actually put work, on purpose, because the network, the origin, and the doors already know the difference.&lt;/p>
&lt;p>I call the shape an &lt;strong>AI CDN&lt;/strong> because a CDN was always a scheduling problem. Put the object near the person who needs it, behind the right door. The object used to be a file. Now it is an answer, a commit, or an agent that is allowed to change the company.&lt;/p>
&lt;p>You do not get that without an origin that already lives in more than one environment. I could not buy that for the object store I wanted, so I &lt;a class="link" href="https://github.com/rajsinghtech/garage-operator" target="_blank" rel="noopener"
>wrote garage-operator&lt;/a> and we still maintain it.&lt;/p>
&lt;p>The runbook is &lt;a class="link" href="https://github.com/keiretsu-labs/kubernetes-manifests" target="_blank" rel="noopener"
>the manifests&lt;/a>. This post is the claim.&lt;/p>
&lt;h2 id="a-control-plane-made-of-environments">&lt;a href="#a-control-plane-made-of-environments" class="header-anchor">&lt;/a>A control plane made of environments
&lt;/h2>&lt;p>&lt;strong>Ottawa&lt;/strong> is where the company writes. Source control, login, the desks the agents sit at. If you are going to change how the system works, you change it here. Other environments can keep running without it. They just cannot ship.&lt;/p>
&lt;p>&lt;strong>Robbinsdale&lt;/strong> is where the house runs. The work here is local on purpose: lights, cameras, media, the copy of family files that should survive a bad day in another city. You do not schedule a coding agent onto the house because the house is not a factory. You schedule the house onto the house.&lt;/p>
&lt;p>&lt;strong>St. Petersburg&lt;/strong> is where the model thinks. The GPUs live there. Ottawa asks questions over the private roads between environments, not over the public internet, and not over the VPN I use from a coffee shop. You schedule inference here because this is the only environment that can afford to be a brain. You do not schedule the company&amp;rsquo;s git here. A GPU site that also has to be headquarters is a single bad afternoon.&lt;/p>
&lt;p>&lt;img src="http://rajsingh.info/p/self-hosting-an-ai-cdn/sites.png"
width="1280"
height="720"
srcset="http://rajsingh.info/p/self-hosting-an-ai-cdn/sites_hu17205453295663711310.png 480w, http://rajsingh.info/p/self-hosting-an-ai-cdn/sites_hu8208017209670912769.png 1024w"
loading="lazy"
alt="Ottawa, Robbinsdale, and St. Petersburg"
class="gallery-image"
data-flex-grow="177"
data-flex-basis="426px"
>&lt;/p>
&lt;p>The control plane is the map. Work has a place. The fabric is what makes &amp;ldquo;schedule it there&amp;rdquo; a real sentence instead of a hope.&lt;/p>
&lt;h2 id="the-hallway-and-the-badge">&lt;a href="#the-hallway-and-the-badge" class="header-anchor">&lt;/a>The hallway and the badge
&lt;/h2>&lt;p>The three environments are one private network. The house routers are meshed. Each cluster tells its local router what it owns. Ottawa talking to St. Petersburg should feel like a hallway.&lt;/p>
&lt;p>Identity is a different layer. People, laptops, and the tools that talk to the API prove who they are. That is the badge. I used to use the badge as the road too. &lt;a class="link" href="http://rajsingh.info/p/tailscale-operator/" >I wrote that version&lt;/a>. For three environments it is the slow way. People get a badge. Work gets a hallway.&lt;/p>
&lt;p>Internal is not a lock. If a name is on the house network, it is on the house network. The lock is which environment you were allowed to enter.&lt;/p>
&lt;h2 id="why-we-wrote-an-operator">&lt;a href="#why-we-wrote-an-operator" class="header-anchor">&lt;/a>Why we wrote an operator
&lt;/h2>&lt;p>A CDN without a multi-environment origin is a website with extra YAML. Garage is that origin. &lt;strong>garage-operator&lt;/strong> is how it shows up in git.&lt;/p>
&lt;p>Off the shelf, the object store is a binary and a layout you edit by hand. That does not survive three environments and agents that open pull requests. The operator exists because this estate needed buckets, keys, and nodes to be files someone can review. We still maintain it because we run it — federation across environments, disks that stay on the machine they belong to, a gateway that keeps its identity when a pod moves.&lt;/p>
&lt;p>One estate. A copy in each environment. Applications never talk to a disk. They talk to a gateway in the environment they were scheduled into. If that environment&amp;rsquo;s disks are gone, the gateway still reads from one that is up.&lt;/p>
&lt;p>That is where database history, images, volume snapshots, and agent artifacts actually live. Without it you can schedule work. You cannot keep it.&lt;/p>
&lt;p>&lt;img src="http://rajsingh.info/p/self-hosting-an-ai-cdn/garage.svg"
loading="lazy"
alt="Distributed S3"
>&lt;/p>
&lt;h2 id="which-door-the-work-is-allowed-to-use">&lt;a href="#which-door-the-work-is-allowed-to-use" class="header-anchor">&lt;/a>Which door the work is allowed to use
&lt;/h2>&lt;p>A homelab that only exists on a VPN is a clubhouse. A company has a street.&lt;/p>
&lt;p>Every environment has three doors: the public internet, the house, and the tailnet. Names that only live in Ottawa stay in Ottawa. Pretending they are global when the other environments have no backend is not scheduling. It is a coin flip.&lt;/p>
&lt;p>The model is the example I care about. The settings page can be on the street, behind a login. The model itself is not. Inference was scheduled into St. Petersburg. The public internet does not get a seat in that room.&lt;/p>
&lt;p>Steer people with DNS. Steer packets down the hallway. The thing I have not built yet is the obvious next scheduler: which environment should &lt;em>this agent&lt;/em> work in. The model already works that way. Brain in St. Petersburg. Front desk in Ottawa. Hallway in between.&lt;/p>
&lt;p>&lt;img src="http://rajsingh.info/p/self-hosting-an-ai-cdn/three-doors.svg"
loading="lazy"
alt="Three doors"
>&lt;/p>
&lt;h2 id="the-agent-is-work-you-schedule">&lt;a href="#the-agent-is-work-you-schedule" class="header-anchor">&lt;/a>The agent is work you schedule
&lt;/h2>&lt;p>An agent will run whatever it just wrote. That is the job. It does not get the building, and it does not get to pick the environment.&lt;/p>
&lt;p>Every session is a small virtual machine. Its own kernel. Set the carpet on fire. When the session dies, the room dies. A few rooms are allowed to badge onto the tailnet from inside. Most are not. If every intern is a VPN node, you did not schedule them. You hired them as the network.&lt;/p>
&lt;p>The other half is the handshake. This summer the popular coding agents did not need to break out. They wrote a file, and a trusted program on the laptop ran it later. Here the handshake is a merge. The agent can wreck its office. The company only changes when I accept the commit.&lt;/p>
&lt;p>&lt;img src="http://rajsingh.info/p/self-hosting-an-ai-cdn/sandbox-merge.svg"
loading="lazy"
alt="Agent, office, merge"
>&lt;/p>
&lt;p>If it is not in git, it does not last. Agents read the repo because the company is files. Checks run before merge. I am not trying to fire myself. I am trying to make the floor small enough that I can still walk it at night.&lt;/p>
&lt;p>Databases are work you schedule too. They are files in the repo. Their history goes to the origin, not a tarball on a laptop. A volume is not backed up because a schedule is green. It is backed up when you have restored it into a new disk and it came back.&lt;/p>
&lt;p>You can rent a coding agent. You can rent a GPU. You can rent a locked room. All three got good. You cannot rent a written copy of how &lt;em>you&lt;/em> operate, plus a way to put work in the environment it belongs in, as the right person, with an origin that already exists in three cities.&lt;/p>
&lt;p>That last part is why garage-operator exists. We wrote it so this control plane had somewhere to put the bytes. We still maintain it because the bytes are still the point.&lt;/p>
&lt;p>The &lt;a class="link" href="https://github.com/keiretsu-labs/kubernetes-manifests" target="_blank" rel="noopener"
>manifests&lt;/a> are how it is wired. This post is what the lab turned into.&lt;/p></description></item></channel></rss>