Skip to main content

Multi-tenant isolation for agencies

Inspect 1.36

The strongest tenant boundary here is structural, not configured: every workload cluster gets its own dedicated control plane, its own etcd, and its own certificate authorities, so one client's cluster has no shared component through which it could reach another's. This is the : one Syself-operated management cluster builds and repairs many independent workload clusters, and nothing is shared across them. For an agency, that is the default, and it is why client-per-cluster is the recommended isolation model. This page covers that boundary and, when you must share a cluster, how to isolate tenants inside one.

The structural boundary: a cluster per client#

Because each cluster's control plane, etcd, and CAs are its own, there is no shared component to attack: a compromise or a misconfiguration in one client's cluster cannot reach another's, because there is nothing in common to cross through. The servers run in the client's own (or your) Hetzner account, and Syself manages only the lifecycle. This also keeps everything else clean:

  • Audit scope per client. Each client's cluster produces its own audit streams, so an audit of one client never touches another's data.
  • Clean hand-off. A client can take their cluster with them; there is no shared state to untangle.
  • Contained failures. A bad deploy, a runaway workload, or a breach stays inside one client's cluster.

The trade-off is running more clusters. That is what and GitOps are for: they let you operate a fleet of isolated clusters from one place, so isolation does not mean managing each one by hand.

Tenant-per-namespace, when you share a cluster#

Sometimes you genuinely want several tenants in one cluster (internal teams, or clients small enough that a cluster each is overkill). Namespace isolation is weaker than a cluster boundary, because all tenants share one control plane, one etcd, and one kernel, but layered controls make it solid for tenants you trust well enough. Give each tenant a namespace and apply all of:

  • A default-deny so one tenant's pods cannot reach another's.
  • A so one tenant cannot starve the cluster.
  • A level (baseline or restricted) so a tenant cannot run a privileged pod that escapes the namespace.
  • Per-namespace so a tenant's users see only their namespace.

Apply every one. If you skip the NetworkPolicy, tenants can reach each other's pods. If you skip the Pod Security level, a tenant can break out with a privileged pod.

Hostile tenants need a stronger boundary#

Namespace isolation assumes tenants are not actively attacking each other, because they still share a kernel. For untrusted or mutually hostile tenants in a shared cluster, add VM isolation: run their workloads under the , which puts each pod in its own VM with its own kernel, on bare-metal nodes. A container escape then stays inside the guest kernel. When tenants are genuinely adversarial and the stakes are high, prefer a cluster each; the structural boundary is the one you do not have to configure correctly.