You Gave Someone cluster-admin and Now You Regret It
You’ve got a k3s node — maybe two — humming away in your homelab. Your friend wants to deploy a side project. Your partner wants to spin up a wiki. A dev environment needs to share the same cluster because spinning up another VM feels like overkill.
So you do the sensible thing: you create a kubeconfig and hand it over. Then you realize you gave them cluster-admin. Now they can list your secrets. Delete your ingresses. Accidentally kubectl delete ns production (yes, even in a homelab, this hurts).
Bare RBAC roles are the other option — and if you’ve tried to handcraft a Role and RoleBinding that gives someone “everything they need but nothing they shouldn’t have,” you know it’s a miserable experience. You’ll forget to add services/finalizers or some other nonsense and spend 45 minutes debugging why helm install keeps erroring.
There’s a better path: Capsule.
What Capsule Actually Is
Capsule is a Kubernetes operator built by Clastix. It introduces a Tenant CRD that wraps a set of namespaces under a logical owner boundary. From the tenant owner’s perspective, they feel like a cluster-admin for their slice of the cluster. From the actual cluster’s perspective, they’re locked inside a very well-defined box.
The core idea:
- Tenants own namespaces. Multiple namespaces can belong to one tenant.
- Tenant owners can create/delete namespaces, deploy workloads, configure RBAC within their boundary — but can’t see or touch other tenants’ resources.
- Cluster-level resources (nodes, storage classes, ingress classes, container registries) are controlled by what the tenant definition allows — not by the tenant themselves.
It’s not virtualization. It’s not separate API servers. It’s soft multi-tenancy with real enforcement — good enough for the homelab case where you trust people, but not that much.
Installing Capsule on k3s
Standard Helm install. Nothing exotic.
helm repo add projectcapsule https://projectcapsule.github.io/chartshelm repo updatehelm install capsule projectcapsule/capsule \ --namespace capsule-system \ --create-namespace \ --version 0.9.3Wait for the controller pod to come up:
kubectl -n capsule-system get pods -wThat’s it. No CRD pre-install dance, Helm handles it. At this point the cluster runs exactly as before — Capsule only activates when you start creating Tenant objects.
k3s-specific note
k3s ships with Traefik as the default ingress controller. When you configure ingressOptions in your Tenant spec, use traefik as the class name unless you’ve replaced it with ingress-nginx or similar. Capsule is agnostic — it just needs you to be explicit.
Your First Tenant
Let’s make a tenant for Alice, who wants to run a couple of apps.
apiVersion: capsule.clastix.io/v1beta2kind: Tenantmetadata: name: alicespec: owners: - name: alice kind: User
namespaceOptions: quota: 3 # Alice can create up to 3 namespaces
resourceQuotas: scope: Tenant items: - hard: requests.cpu: "4" requests.memory: 8Gi limits.cpu: "8" limits.memory: 16Gi
limitRanges: items: - limits: - type: Pod max: cpu: "2" memory: 4Gi min: cpu: 50m memory: 64Mi
networkPolicies: items: - podSelector: {} policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: capsule.clastix.io/tenant: alice egress: - to: - ipBlock: cidr: 0.0.0.0/0
ingressOptions: allowedClasses: matchLabels: - traefik allowedHostnames: allowedRegex: "^.*\\.alice\\.lab\\.local$"
storageClasses: allowedRegex: "^(local-path|longhorn)$"
containerRegistries: allowedRegex: "^(docker\\.io|ghcr\\.io|registry\\.alice\\.lab\\.local)$"Apply it:
kubectl apply -f tenant-alice.yamlkubectl get tenantsNow when Alice creates a namespace, she has to authenticate as the alice user in her kubeconfig. Capsule’s webhook intercepts the namespace creation, checks if she’s a tenant owner, and if yes — mutates the namespace to attach the tenant label and injects the resource quotas automatically.
# As alice (her kubeconfig context)kubectl create namespace alice-blogkubectl create namespace alice-wikiBoth namespaces get:
capsule.clastix.io/tenant: alicelabel- Resource quotas cloned from the Tenant spec
- The network policies applied automatically
Alice can do whatever she wants inside those namespaces. She cannot see namespaces that don’t have her tenant label. She cannot use storage classes or ingress classes not in the allowlist. If she tries to pull an image from quay.io, the admission webhook will reject the pod.
Capsule Proxy: Making kubectl Feel Right
Here’s the awkward part of vanilla Capsule: Alice runs kubectl get namespaces and sees every namespace in the cluster. She can’t touch them, but she can see them. For a homelab this might be fine. For anything approaching real multi-tenancy it feels wrong.
Capsule Proxy fixes this. It’s a lightweight HTTPS gateway that sits in front of the Kubernetes API server and rewrites list/watch responses to only include tenant-owned resources.
helm install capsule-proxy projectcapsule/capsule-proxy \ --namespace capsule-system \ --set options.oidcUsernameClaim=email \ --version 0.9.0Alice’s kubeconfig points at the Capsule Proxy endpoint instead of the real API server. She runs kubectl get ns and sees only alice-blog and alice-wiki. Nodes, other tenants’ namespaces, cluster-level configs — all invisible.
The proxy also handles kubectl get nodes — you can configure whether tenant users see nodes or not. In a homelab, honestly, you probably let them see nodes. It’s not sensitive and it’s useful for debugging “why won’t my pod schedule.”
Locking It Down with Pod Security Standards
Capsule 0.8.x+ integrates with Kubernetes Pod Security Standards (PSS). You can enforce the restricted profile per-tenant, which blocks:
- Privileged containers
hostPID,hostNetwork,hostIPChostPathvolumes- Running as root
Add this to your Tenant spec:
podOptions: securityContext: forbiddenSysctls: - net.ipv4.ip_local_port_range allowedUnsafeSysctls: []
# PSS enforcement via namespace labels (Capsule copies these to owned namespaces) namespaceOptions: additionalMetadata: labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/warn: restrictedCapsule will inject these labels onto every namespace the tenant owns. The Kubernetes admission controller does the rest. Alice can’t deploy a container running as root — she’ll get a clear error message telling her why.
This is where Capsule earns its keep over hand-rolled RBAC. Getting PSS enforcement + resource quotas + network isolation all wired up correctly via raw RBAC and LimitRange objects across dozens of namespaces is a weekend you don’t want to have.
Pairing Capsule with ArgoCD
If you’re running ArgoCD on the same cluster (and you should be — it’s great), you can give each tenant their own ArgoCD AppProject. This means Alice can only deploy to her namespaces, and she can only pull from approved Git repos.
apiVersion: argoproj.io/v1alpha1kind: AppProjectmetadata: name: alice namespace: argocdspec: description: Alice's tenant project
sourceRepos: - "https://github.com/alice/*" - "https://gitea.alice.lab.local/*"
destinations: - namespace: "alice-*" server: https://kubernetes.default.svc
clusterResourceWhitelist: [] # no cluster-level resources
namespaceResourceBlacklist: - group: "" kind: ResourceQuota - group: "" kind: LimitRange
roles: - name: alice-deployer policies: - p, proj:alice:alice-deployer, applications, *, alice/*, allow groups: - aliceThe destinations pattern alice-* matches any namespace Alice creates that starts with her tenant name. Capsule enforces the namespace naming convention via the namespaceOptions.forbiddenLabels or you can just document the convention and trust that Alice isn’t malicious — she’s your friend, not a threat actor.
With this setup: Alice pushes to her Git repo → ArgoCD picks it up → deploys only to alice-* namespaces → Capsule quotas and network policies apply. Clean.
How Capsule Compares to the Alternatives
You’ve got a few other options in this space and they’re worth knowing:
vCluster gives each tenant an actual virtual Kubernetes cluster — separate API server, separate scheduler, the works — running as pods in the host cluster. Isolation is near-complete. But it’s heavier. Each vCluster burns meaningful RAM even at idle. If you’re running this on a single NUC with 32 GB, giving three tenants vClusters means you’ve handed out maybe 6 GB of RAM just for control planes. Capsule’s overhead is one operator pod. If your threat model doesn’t require virtual-cluster-level isolation, Capsule wins on resource efficiency.
HNC (Hierarchical Namespace Controller) is a simpler Google-originated project that lets namespaces have parent-child relationships and propagate RBAC/policies downward. Great for organizing namespaces logically, but it doesn’t give you the tenant-scoped resource quotas, registry enforcement, or ingress allowlisting that Capsule does out of the box. HNC is a building block; Capsule is a solution.
kiosk / Loft are commercial or commercially-backed products that solve similar problems with more polish and a web UI. If you want a management console for your homelab tenants, Loft is worth a look. But you’re adding an external dependency and potentially a paid tier if you scale up. Capsule is pure upstream, pure open source, and honestly the YAML surface area isn’t that bad once you’ve read through it once.
For a homelab k3s cluster where you want to give 2-5 people their own space without spinning up another VM or paying for anything: Capsule is the call.
The Honest Limits
Capsule is soft multi-tenancy. It uses admission webhooks to enforce policy. That means:
- If someone has
kubectl execinto a container, Capsule can’t stop them from doing network tricks at the OS level that bypass network policies (depending on your CNI) - A determined, compromised container could still try to break out via kernel exploits — Capsule doesn’t help there
- etcd isn’t partitioned — tenant data lives in the same etcd as everyone else
For a homelab with trusted users who just want isolation because “accidents happen” — this is completely fine. You’re not running a public cloud. You’re trying to stop your brother-in-law from accidentally deleting your Jellyfin namespace, not defending against a nation-state attacker.
Also worth knowing: Capsule works best when your users authenticate with distinct identities. If everyone’s sharing the same kubeconfig, Capsule can’t tell them apart. In k3s, you can create separate ServiceAccounts or use kubeconfig files with distinct user certificates. The Capsule docs have a good walkthrough on generating per-user kubeconfigs from k3s’s built-in CA.
Should You Bother?
If your k3s cluster is just for you: no. Capsule adds complexity you don’t need.
If you want to share your cluster with one or two trusted people and you’re currently doing it by handing out cluster-admin or painfully hand-writing RBAC: yes, absolutely. The install is 10 minutes, the Tenant CRD is readable YAML, and the protection you get — namespace isolation, resource quotas, registry allowlisting, ingress class control — would take you a weekend to replicate with raw Kubernetes primitives.
The sweet spot is “I’ve got a serious enough homelab that I’m running k3s and ArgoCD, and I want to give a friend their own slice without babysitting them.” That’s exactly what Capsule was built for.
Add Capsule Proxy if you care about tenants seeing a clean kubectl experience. Skip it if you don’t. Pair it with ArgoCD AppProjects if you’re doing GitOps. Add PSS restricted labels if you want to sleep soundly knowing nobody’s running privileged containers in your cluster.
It’s the multi-tenancy setup that doesn’t make you regret adding housemates to your homelab.