0007 Per-user workspaces on Kubernetes, a shared workbench in Compose¶
Context and problem statement¶
Workspaces run user code: notebooks, terminals, coding agents, scaffolding jobs. In a team installation one user's code must not read another user's files, credentials or compute, and revoking a grant must stop the workspace. On a single developer machine the same isolation would cost resources and add nothing. How should Kionga provision workspaces?
Decision drivers¶
- Isolation between users where several users share an installation.
- Workspace lifecycle driven by the administrator's grant: create, resize, suspend, revoke.
- Bounded compute and storage per user.
- Separate browser origins so one workspace cannot use another user's cookies.
- A simple single-user experience in Compose.
Considered options¶
- One
KiongaWorkspaceper access grant on Kubernetes, a shared workbench in Compose - A shared workbench everywhere
- Per-user containers on the Docker host
Decision outcome¶
Chosen option: One KiongaWorkspace per access grant on Kubernetes, a shared
workbench in Compose, because isolation is enforced where several users exist and
the single-user stack stays simple.
A change to a user's access writes a command to mlaiops.workspace.commands through
the outbox. The integration worker creates or updates the KiongaWorkspace (and
deletes it when access is deleted). The WorkspaceReconciler in
platform/go/internal/operator/workspace_controller.go turns it into one Deployment
with Jupyter and the IDE sharing a PVC, a Service, and a credentials Secret, with the
granted CPU, memory and GPU split between the containers. A suspended grant scales the
pod to zero.
Consequences¶
- Good, because each workspace has its own PVC, credentials and resource limits, runs
as non-root with the runtime-default
seccompprofile, and lives in a namespace with a ResourceQuota and default-deny network policies. - Good, because revocation is part of reconciliation, not a manual step.
- Good, because with
KIONGA_WORKSPACE_BASE_DOMAINeach workspace gets its own host; production startup checks require that domain to be distinct from the console host and to share its registrable site. - Good, because tenant storage admission rejects
hostPathand limits StorageClasses (WORKSPACE_ALLOWED_STORAGE_CLASSES). - Bad, because isolation requires Kubernetes. Compose shares one
jupyter-datavolume between the workbench and the IDE, and is not a multi-user boundary. - Bad, because a workspace volume is
ReadWriteOnce; on zonal block storage it is tied to one availability zone.
Confirmation¶
platform/go/internal/operator/workspace_controller_test.go and
platform/go/internal/operator/storage_admission_test.go cover reconciliation and
storage admission; TestProductionWorkspaceDomains covers the domain rules;
deploy/scripts/e2e-kind.sh exercises workspaces on a kind cluster.
Pros and cons of the options¶
Per-grant KiongaWorkspace on Kubernetes¶
- Good, because Kubernetes provides the isolation primitives (namespaces, quotas, network policies, PVCs).
- Bad, because it needs a cluster, an Ingress with wildcard TLS, and storage classes.
Shared workbench everywhere¶
- Good, because it is the simplest setup.
- Bad, because users would share files and credentials.
Per-user containers on the Docker host¶
- Bad, because provisioning them would need the Docker socket, which is root-equivalent on the host, so the isolation would be weaker than the trust it needs.
More information¶
Written after the fact. Evidence: platform commit f82e768 (2026-07-20) added the
workspace reconciler; the CRD is deploy/config/crd/mlaiops.io_kiongaworkspaces.yaml;
the dispatcher mapping is in platform/go/internal/integrations/dispatcher.go. The
per-user Docker container option is not recorded in the history; it is listed because
the Compose stack's Docker socket boundary
(risk R1) rules it out.