sandboxlab chart
Installs the sandbox control plane: the API, the console, and the proxy that reaches into a sandbox.
A sandbox here is a namespace. Creating one is a cluster-scoped write, it is deleted as a unit when its time is up, and it holds nothing the control plane needs to remember — which is why this chart has no database, no PersistentVolume and no state to back up.
Installing
From the published repository:
helm repo add sandboxlab https://www.chenshaowen.com/sandboxlab
helm repo update
helm install sandbox sandboxlab/sandbox \
--namespace ops-system --create-namespace \
--set publicURL=https://sandbox.example.com
Or from a checkout:
helm install sandbox ./charts/sandbox \
--namespace ops-system --create-namespace \
--set publicURL=https://sandbox.example.com
The control plane lands in ops-system by default (namespaceOverride).
That is not the release namespace, deliberately: this is infrastructure, and the
things it manages — the sandboxes — get namespaces of their own.
Read the key:
kubectl -n ops-system get secret sandbox-apikey \
-o jsonpath='{.data.api-key}' | base64 -d; echo
helm install prints the same instructions in its notes, naming the Secret the
key is in — sandbox-apikey unless existingSecret named another. The notes do
not print the key itself: the chart cannot read one it was not given, and one it
was given is a credential someone else chose, so the notes point at the Secret
rather than echoing it.
Prerequisites
| A cluster | Any. Nothing here is provider-specific. |
| Cluster-scoped RBAC to create | A sandbox is a namespace, so the control plane needs create/delete on namespaces cluster-wide. See RBAC. |
| Something to publish with | An Istio gateway, an Ingress, or a tunnel. Or nothing, and a port-forward. |
Reaching it
Three routes reach the deployment, and you pick one:
A tunnel — what the sandboxlab action does. publicURL is
the tunnel's hostname, basePath is set, and istio.enabled=true writes the
one VirtualService that puts the console and every sandbox behind it.
An Ingress — ingress.enabled=true and ingress.host. Point it at the
control plane's Service and set publicURL to the same host.
A port-forward — nothing to configure. Leave publicURL empty and the API
reports relative addresses:
kubectl -n ops-system port-forward svc/sandbox 8080:80
The base path
basePath is a path prefix the whole deployment is served under: /sandboxlab
means the console is at https://host/sandboxlab and a sandbox at
https://host/sandboxlab/sandbox/<id>/<port>/. Leave it empty to serve at the
root.
It is not cosmetic. The environment the sandboxlab action builds publishes everything on one hostname, and the path is what tells the console's own routes from an app's — so the base path is how the control plane and a sandbox can share an address without a second hostname.
RBAC
The control plane's ClusterRole grants, cluster-wide:
namespaces: get, list, watch, create, update, patch, deletedeployments,services,resourcequotas,limitranges,pods,configmaps,persistentvolumeclaims,networkpolicies: the full set, inside the namespaces it createspods/log: get only, forsandbox logs. A subresource, so it is not covered by thepodsgrant above.secrets: get, list, create, update.getreads the deployment's own key Secret;list,createandupdateare the per-sandbox keys — each sandbox has one, it lives in the sandbox's namespace, and resolving a presented key is a cluster-wide listing of them. There is nodelete: a sandbox's key goes when its namespace does. What this reaches is worth saying plainly — the control plane can read every sandbox's key, which is the same reachpods/execbelow already gives it.pods/exec: get, create. This is the one that letssandbox execand the file endpoints run anything inside a sandbox, so it is worth knowing it is there: whoever holds the deployment's key can execute in any sandbox this ServiceAccount can reach.metrics.k8s.io/pods: get, list — the console's Metrics panel. Resource usage lives in its own API group, and itspodsis a different resource from corepods, so the grant above does not cover it. Without this, reading usage is a 403; a cluster with no metrics-server answers 404 for the group, which the control plane reports as usage being unavailable.events: get, list, watch — read-only. This is what explains a sandbox that is not working: an image pull that failed, a probe that killed the container, a pod that was never scheduled. The control plane reports events, it never creates one.
delete on namespaces is the one worth reviewing: deleting a namespace
reclaims everything in it. That is the design — one operation, nothing to miss —
and it is also the most destructive thing this component can do.
rbac.create=false skips the ClusterRole and ClusterRoleBinding if you supply
your own; the chart then expects a ServiceAccount named
<release>-sandbox-control-plane to already have the grants.
The catalog
The templates a sandbox can be created from are compiled into the control plane:
agent-infra (the AIO Sandbox, the one that boots ready with a browser and a
desktop), agent-sandbox (the reference runtime from
kubernetes-sigs/agent-sandbox, whose image must be built and pushed first),
opensandbox (alibaba/OpenSandbox's code-interpreter image), e2b (E2B's
code-interpreter image) and cubesandbox (TencentCloud/CubeSandbox's base
image). Read them with sandbox catalog or GET /api/v1/catalog.
More can be added to a running deployment without a release, through the API or the console:
# sandbox catalog add internal-tool.yaml
id: internal-tool
title: Internal tool environment
image: registry.example.com/internal/tool:1.2.3
ports:
- name: web
port: 8080
ttlDefault: 30m
ttlMax: 4h
An id that already exists is replaced, so adding is also how a template is
edited. The additions live in memory only: the control plane returns to the
compiled-in templates when it restarts, so there is nothing to configure here in
the chart and nothing to persist. sandbox catalog rm <id> removes one.
Uninstalling
helm uninstall removes the control plane and the sandboxes it created. A
pre-delete Job deletes every namespace carrying
app.kubernetes.io/managed-by=sandboxlab, which is every sandbox.
That Job exists because a sandbox is a namespace of its own: nothing in the
release owns it, so without it an uninstall would leave every sandbox behind —
running, costing resources, and reachable by nobody. Set cleanup.enabled=false
to keep them, and delete them by hand.
Values
| Value | Default | What it is |
|---|---|---|
namespaceOverride |
ops-system |
Where the control plane runs. |
image.repository / image.tag |
chart appVersion | The control plane image. |
apiKey |
generated | The key every call carries. Empty generates one and reuses it across upgrades. |
existingSecret |
— | A Secret holding the key instead, with the same api-key field. Keeps the value out of the release; set it or apiKey, not both. |
publicURL |
— | The address the API reports sandbox URLs under. Empty reports relative ones. |
basePath |
— | A path prefix the deployment is served under. |
sandboxNamespacePrefix |
sbx- |
Prefix of a sandbox's namespace. |
defaultTTL / maxTTL |
1h / 8h |
Sandbox lifetimes. |
maxSandboxes |
0 (no ceiling) |
How many may exist at once. |
reapInterval |
30s |
How often expired sandboxes are collected. |
execTimeout |
1m |
How long a command run in a sandbox may take by default. |
execTimeoutMax |
10m |
The longest a caller may ask for. |
maxFileBytes |
2097152 |
The largest file the API reads or writes. |
dataPlane |
true |
Serve /sandbox/<id>/<port>/ by proxying into a sandbox. |
istio.enabled / istio.gateway |
false / — |
Write the VirtualService. The gateway must already exist. |
ingress.enabled / ingress.host |
false / — |
Write an Ingress instead. |
cleanup.enabled |
true |
Delete the sandboxes on uninstall. |
replicaCount |
1 |
One, because each replica runs its own reaper. |
kubectl explain and values.yaml are the exhaustive sources; every field is
commented where it is defined.
What it does not create
- A Gateway. An Istio gateway is shared by everything on a cluster; the
chart points at one (
istio.gateway) and fails to render without it rather than installing a route that attaches to nothing. - A registry or an object store. The control plane has no state to keep and no images to build.
- TLS. Terminate it at the tunnel, the load balancer, or the Ingress.
This page is rendered from charts/sandbox/README.md in the repository.