Bring Your Own Cloud: The Infrastructure Registry

By Arshad Ansari

AEGIS's public release is a week out — though the repo has quietly been up for a couple of days already, building in the open. Before that release could happen, I had to answer a question I'd been dodging since the origin story: what does a self-hosted system do about infrastructure credentials when the person running it is no longer me?

My answer for two years was simple, because it was wrong: the credentials were mine, baked into config files, aimed at my three-node Docker Swarm at home. That works for a system with exactly one user. It doesn't survive a fork.

For a system that's meant to be forked, there's only one honest design. The user brings their own infrastructure, the system stores the credentials encrypted, and nothing in the code assumes a vendor. Not AWS, not GCP, not my swarm, not anyone's.

That design has a name in AEGIS now: the infra registry.

One table, four kinds of infrastructure

The registry is deliberately unglamorous: an infra table in Postgres, a services/infra.py service around it, and an Infra page in the admin panel. Each row is a piece of infrastructure the agents are allowed to know about:

  • SSH hosts — machines the system can reach, each with its own key.
  • Docker Swarm clusters — what the HomelabConnector operates over SSH.
  • Kubernetes clusters — kubeconfig per entry, kubectl operations gated behind it.
  • Cloud accounts — AWS multi-profile credentials files or GCP service-account JSON. Provisioning one is just an identity check: can the system authenticate as you, yes or no.

A Kubernetes entry can reference a cloud account instead of carrying inline credentials, which matters more than it sounds — an EKS cluster's identity is really an AWS identity, and pretending otherwise means storing the same secret twice.

Every credential — SSH keys, kubeconfigs, cloud auth — is encrypted at rest, per entry. And every entry has a read_only flag: a mutation gate. A cluster you only ever want observed gets read_only: true, and the agents can list and describe to their hearts' content but never change anything. Trust is granted per entry, not per system.

Credentials are radioactive

Encrypting secrets at rest is the easy half — it's a solved problem and mostly a matter of not improvising. The hard half is discipline: a credential value must never appear anywhere a human might read it. Not in logs. Not in a traceback when an SSH connection fails. Not in the admin API response that has to round-trip everything else about the entry. Once you take this seriously, you find that most of your codebase's error paths were built on the cheerful assumption that printing state is free.

It isn't. A secret that has been printed is a secret that has to be rotated, and a personal system that leaks your kubeconfig into its own logs is worse than no system at all — it has your keys and your trust.

So values go in through the admin UI and never come back out. Everything downstream references entries by name and lets the service layer do the decryption at the last possible moment.

The exec-plugin gotcha

Here's the one that genuinely surprised me. You'd think storing a kubeconfig encrypted is the whole job for Kubernetes support. It isn't, because modern managed Kubernetes doesn't put tokens in kubeconfigs anymore.

An EKS or GKE kubeconfig typically uses exec-plugin auth: instead of a credential, it holds a command — shell out to aws or gcloud, mint a short-lived token, hand it to kubectl. Which means the kubeconfig you carefully encrypted is useless unless those vendor binaries exist inside the AEGIS container.

Baking aws and gcloud into everyone's image would be the easy fix and the wrong one — most people running a personal orchestration system on a home server don't have an EKS cluster, and they shouldn't pay the image weight for mine. So it's a build argument: EXTRA_CLOUD_CLIS="aws gcloud" at image build time, and you opt into exactly the CLIs your kubeconfigs need. If your cluster is a k3s box in a cupboard, you build nothing extra.

It's a small thing, but it's the shape of the whole exercise: the vendor assumption doesn't get deleted, it gets moved to the edge and made explicit.

The same registry also grew a stranger tenant: the coding host. A RemoteScriptConnector that SSHes to a designated machine and runs a coding CLI there — claude or kimi engines — configured DB-first from the admin UI. The machine, the engine, the accounts: all rows, not code.

Cutting my own vendors out

The registry is the welcoming half of the story. The other half happened the same day, and it was harder: removing my vendors from the code.

The finance agent used to pull market regimes and forecasts from my personal ClickHouse — the output of a trading pipeline nobody else on earth is running. That integration was load-bearing and completely unshippable. It's gone, replaced by a FinanceConnector that's provider-agnostic and keyless: public web endpoints, Yahoo and Stooq, no API key to acquire, no account to create. Strictly worse than my pipeline for me. Strictly better for everyone else, because it works the moment you bring the stack up.

Telegram went the same way — removed repo-wide, with Slack as the chat channel now. The owner-specific homelab probes, the ones that knew the names of my machines, were stripped. And the whole configuration plane moved out of image-baked files and into the database: personalities, API keys, channels are DB-owned rows edited from the admin UI. The source of truth is no longer a YAML file that ships with my defaults in it. It's your database, starting empty.

Here's the honest part. Deleting your own vendors is the most uncomfortable refactor I know, because you don't find out how much you assumed until the thing is gone. Grep tells you where ClickHouse is named; it can't tell you which parts of the system quietly depend on its shape — its latency, its schema, the fact that it never says no. Every removal surfaced two or three of those. The system that emerged is smaller and more honest, but for a few days in the middle it was neither.

Where it's heading

More providers, obviously — the registry's whole point is that adding a kind of infrastructure shouldn't mean touching the agents that use it. And tighter permissions: read_only is a binary gate today, and a binary gate is a blunt instrument. It wants to become something finer — this entry may restart services but not delete them, that one may read secrets' names but never their values.

But the principle is settled, and I don't expect it to move: AEGIS never brings a cloud. You do.

The repo is already up at github.com/hikmahtech/aegis, MIT-licensed, with the full release a week out. The project lives at /aegis if you want the longer tour.

Building something data-heavy? Let's talk.