All posts
Deep dive 2 September 2026 · engineering · providers

How Flui models infrastructure as capabilities instead of providers

Two clouds can look identical and behave nothing alike. Here is the interface that absorbs the difference, what each provider actually declares, and what Flui does when the answer is no.

Dawit Abate

The naive way to support a second cloud provider is to add a branch. The naive way to support a third is to add two more. By the fourth, provider names are scattered through the CLI, the dashboard and the orchestrator, and adding a fifth is a project rather than a module.

That is not a hypothetical failure mode. It is the default one, and it is what the capability model exists to avoid.

The problem is not that clouds differ. It is how they differ.

Two providers can offer what looks like the same product and behave nothing alike.

What sits next to “a server” varies wildly. One provider gives you managed DNS, firewalls, private networks, load balancers and snapshots through a single API. Another gives you a server and very little else. Building a platform on top of either requires substantially different work.

Billing models do not match. One bills by the hour and stops charging the moment you delete the resource. Another bills monthly under contract. Some vendors mix both inside the same catalogue — which matters more than it sounds, because a cluster that adds machines when it runs out of room is only economically sensible on the first kind.

The same idea is named differently. A server lives in a location, a zone or a region depending on who you ask, and those words are not interchangeable: each carries its own meaning about scope, redundancy and price.

And private networks are their own trap. Hetzner’s are zone-scoped: one VNet is tied to exactly one network zone, and servers in fsn1, nbg1 and hel1 can share it because all three sit in eu-central. Scaleway’s are regional: to connect servers in Paris, Amsterdam and Warsaw you need a VNet per region. Same feature name. Different topology. Any code that assumes one shape breaks silently on the other.

What a provider declares

So a provider module in Flui does not implement a grab-bag of methods and hope. It answers a fixed question: what can you do? This is what the Hetzner module returns, abbreviated:

{
  credentialType: 'api_key',
  features: {
    nodeProvisioning: true,
    privateNetworking: true,
    dnsZones: true,
    loadBalancers: true,
    snapshots: true,
    backups: true,
  },
  pricing: { currency: 'EUR', billingCycle: 'hourly', minimumCost: 0.0056 },
  firewall: { backend: 'managed-api', managedEdge: true, supportsSshAllowlist: true },
  vnetTopology: { scope: 'regional', zones: [ /* … */ ], supportsSubnets: true },
  vnetRequired: true,
  crossClusterAllowed: false,
}

Everything above the provider layer — the CLI, the dashboard, the orchestration engine, the application model — reads that declaration. It talks to a provider, never to this specific cloud. Provider differences are absorbed by the module below, not propagated upward.

The capabilities themselves are grouped into levels, and the levels are ordered by how much breaks without them:

  1. Base — server lifecycle. Create, delete, status, list, health. Without this a provider cannot be used at all.
  2. Hourly billing — the economic prerequisite for elastic capacity.
  3. SSH key registry — keys registered on the provider account and attached at creation.
  4. Firewall — rules enforced at the provider edge.
  5. DNS — zones and records Flui can reconcile.
  6. VNet — private networking between nodes.
  7. Power management — start, stop, reboot through the API.

The interesting part is what happens when the answer is no

A capability model that only recorded support would be a feature matrix with extra steps. The useful half is the declared behaviour when something is missing — and specifically, that missing is not the same as fatal:

MissingWhat Flui does
Hourly billingNothing is blocked. Elastic capacity is simply not economically viable, and Flui does not pretend otherwise.
SSH key registryThe bootstrap key is injected through cloud-init user_data instead. Automatic. Cluster creation still works; key rotation costs a reprovision.
Provider firewallThe cluster is created without one, with a warning in the log rather than a blocked operation. Host-level rules still apply.
DNSFlui does not configure records. You do. It says so rather than failing halfway.
VNetNodes are not on a private network and talk over public IPs.
Power managementThe power endpoints are unavailable. Everything else is not.

This is the design decision the whole model rests on, and it is worth stating plainly: Flui is not the lowest common denominator. The alternative — supporting only what every integrated provider supports — would mean a platform that gets worse every time a simpler provider is added. Nobody would be served by that, least of all the users on the capable providers.

What that buys, concretely

Four providers are integrated today, and only two of them are what most platforms would call fully supported.

Hetzner declares everything: hourly billing from roughly €0.0056 an hour, zone-scoped private networks across eu-central, managed DNS, provider firewalls, load balancers, snapshots. It is the reference implementation, and if you are wondering what the model looks like when a provider says yes to every question, that is it.

Scaleway declares everything Hetzner does, and one thing no other module declares: inference. Flui can reach models on the same credential it already holds for compute, so pointing it at a Scaleway account hands it both. That is not a support tier — it is a capability that happens to be present on exactly one provider, which is precisely the case a tier system has nowhere to put and a per-capability declaration expresses in one field.

OVHcloud is the case that shows the model is not a two-tier ranking in disguise. It arrives through OpenStack — Keystone for auth, Nova for servers, Neutron for networks — and declares nodeProvisioning: true, hourly billing from €0.0104 an hour, and a real private network its nodes get an address on. By the usual reckoning that is a first-class cloud. It also declares no managed DNS and no managed firewall edge, the latter for a reason no abstraction should paper over: Neutron security groups exist on paper but ship with a quota of zero, so the API is there and unusable. Flui does not report that as a firewall it drives. It routes the rules to the same host-level nftables backend a machine of your own uses, and says so. One provider, yes on the rows most platforms gate on and no on two they assume — which is exactly the shape a per-capability declaration exists to express and a support tier cannot.

Your own servers are the same story taken to its conclusion. Bring-your-own-server has no provisioning API — that is what it means — so nodes and firewalling are driven over SSH, and private networking is whatever you wired yourself. It is not a degraded mode bolted on for completeness. It is a provider module like any other, declaring ssh as its credential type and manual as its network scope, and it is the reason a machine under a desk and a Hetzner node are the same kind of thing to everything above.

The part that is not done

Two constraints are worth naming, because a post like this is otherwise easy to read as a claim of generality it has not earned.

Nodes run Ubuntu 24.04 on x86_64. ARM is not supported.

And a cluster still belongs to one provider. The capability model makes a provider substitutable; it does not yet make a single workload composed — you cannot take compute from one cloud and storage from another inside one cluster today. Composition happens between clusters. That gap is the interesting work, and pretending it is closed would waste the credibility this model is being built to earn.

What the model does buy, already, is that closing it is a matter of extending an interface rather than unpicking four providers’ worth of branches from the rest of the system.

Try Flui for a day More posts