Skip to content
Go back

Capsule: Multi-Tenancy for Home Lab k3s

By SumGuy 10 min read
Capsule: Multi-Tenancy for Home Lab k3s

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:

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.

Terminal window
helm repo add projectcapsule https://projectcapsule.github.io/charts
helm repo update
helm install capsule projectcapsule/capsule \
--namespace capsule-system \
--create-namespace \
--version 0.9.3

Wait for the controller pod to come up:

Terminal window
kubectl -n capsule-system get pods -w

That’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.

tenant-alice.yaml
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: alice
spec:
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:

Terminal window
kubectl apply -f tenant-alice.yaml
kubectl get tenants

Now 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.

Terminal window
# As alice (her kubeconfig context)
kubectl create namespace alice-blog
kubectl create namespace alice-wiki

Both namespaces get:

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.

Terminal window
helm install capsule-proxy projectcapsule/capsule-proxy \
--namespace capsule-system \
--set options.oidcUsernameClaim=email \
--version 0.9.0

Alice’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:

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: restricted

Capsule 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.

appproject-alice.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: alice
namespace: argocd
spec:
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:
- alice

The 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:

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.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Jellyseerr Tagging Workflows for Real Libraries

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts