26 October 2026

Everything gets a cnc ​

Last night at midnight I needed mail on a few domains that have no mail server. Not a mailbox to sit in, just somewhere for messages to land, a way for apps to send, and something an agent can read. This morning it was live, received its first real message with DKIM and SPF passing, and sent a reply in the same thread.

I didn’t install a mail server. I had a cnc built.

What a cnc is ​

cnc is short for command and control. Every time some part of my infrastructure needs managing, it gets a small service of its own whose name ends in cnc. There are seven of them now:

  • domaincnc: domains, DNS, the local resolver and the reverse proxy routes, across three registrars.
  • assistantcnc: the fleet of AI assistant VMs I run for customers. Create, provision, update, back up, destroy.
  • modelcnc: a matrix of every model behind my model router, scored for how smart, how dumb, how fast and how expensive it is, with a pick call that chooses one for a job.
  • browsercnc: real, headed browsers on demand, for agents and apps that need to log in somewhere and stay logged in.
  • gitcnc: moving repositories off GitHub to a self-hosted Forgejo, keeping GitHub as a mirror, and switching dependency updates on per org.
  • backupcnc: every database and volume, off the machine, every night, plus a monthly restore test.
  • mailcnc: the mail from last night.

The first one appeared at the end of August. Four of them are from October. Together they are about 48,000 lines, and I didn’t write them. I described them.

Every one has the same shape ​

That is the part that makes it work. Each cnc is the same three things:

  1. A panel. A web UI I can look at, behind single sign-on, reachable only from the VPN or the LAN.
  2. A REST API, with keys at three levels: read, write and admin.
  3. An MCP server, so any agent can drive it with tools instead of me clicking.

Secrets don’t live in the compose file; they live in a vault, and the service reads them at deploy. Every cnc has a wiki page in the same format: what it is, how it works, its rule, its secrets, its limits. When I ask for a new one I don’t have to explain any of this. “Make a cnc for X” carries the whole convention.

The MCP part is what changed how I work. The agent writing code for me right now has domaincnc, gitcnc and assistantcnc among its tools. When a new project needs a hostname, it doesn’t ask me to go set up DNS. It asks domaincnc.

One thing each, and they ask each other ​

A cnc owns one thing, and nothing else touches that thing directly.

mailcnc needs to add DNS records when it turns mail on for a domain. It doesn’t write DNS. It asks domaincnc, with its own write key. assistantcnc needs two hostnames for every new VM; it gets them from domaincnc, and when a VM is destroyed it removes them there too. backupcnc doesn’t keep a list of what to back up; it reads a label on each container, and the label always wins over the panel. gitcnc decides which repositories the dependency bot runs on, and the bot reports back to gitcnc every hour.

So the rules can live in one place. domaincnc has exactly one: a host marked private must never get a public DNS record, because the service behind it would end up on the internet. That rule is written once, and every other cnc goes through it.

domaincnc also reconciles. If someone edits a DNS record by hand in the Cloudflare dashboard, that counts as drift, and it can be put back. The record lives in the cnc, not in the dashboard.

Why not just install something ​

There usually is something to install. There are backup tools, DNS managers and mail servers. I used some of them for years.

Here is what an off-the-shelf tool usually lacks: it doesn’t know about the rest of my setup. It has its own users, its own idea of what a domain is, its own config file, and no API an agent can use without a wrapper. I end up being the integration layer, copying a hostname from one panel into another.

A cnc built for my setup is small because it only does what I need. mailcnc is about 3,900 lines because Cloudflare already does the hard part; it only has to tell Cloudflare what to do and keep what arrives. backupcnc is about 3,400 lines on top of restic. The work is in fitting the parts together, and that part is exactly what doesn’t come in the box.

The cost of building one used to be the reason not to. That reason is gone. What’s left is deciding what the thing must never do, and that’s the part I still do myself.

A label nobody read ​

When image updates moved to the self-hosted dependency bot that gitcnc switches on, something embarrassing turned up. Sixteen of my stacks had a label asking an image-update watcher to keep an eye on them. The watcher had never run. For seven months the labels said those images were being watched, and nothing was watching them.

The commit that removed them says it in five words: “Diun never ran.” That is the other reason to keep all of this in a few places with panels: a cnc has a screen that shows you what it actually does. A label in a compose file just says what you hoped would happen.

What a new one costs now ​

mailcnc’s first commit was at 23:48. By 07:41 it had a fix for a domain whose DNS it couldn’t read, an SMTP endpoint for apps that only speak SMTP, and a wiki entry saying what the live test showed: real mail in through Cloudflare’s MX, out through the API and SMTP, a reply in the same thread, a message to an outside address.

That’s the price of a cnc now: one evening, then a morning to test it against the real thing. So when something in the stack needs managing, I no longer ask which tool to use. I ask what the cnc should be allowed to touch.