A sandbox Pod reaches this provider only if the scheduler binds it to the virtual node, and the provider serves it only if the Pod carries the right annotations. Both halves are set by sandbox-operator’s runtime mutator; this page documents the contract so it can also be driven by hand.
sandbox-operator’s podruntime mutator runs when a Sandbox selects the
sandboxd runtime (sandbox.cocoonstack.io/runtime: sandboxd, or the
operator’s default runtime mode). It:
nodeSelector: sandbox.cocoonstack.io/runtime=sandboxd, the label
vk-sandbox advertises by default (--node-labels);virtual-kubelet.io/provider with operator Exists
and effect NoSchedule, which covers both this node’s taint and a
co-located vk-cocoon node’s;spec.nodeName is pinned or the node selector already
disagrees – a misrouted sandbox is a failure, not a silent fallback.A Pod that arrives here with sandbox.cocoonstack.io/runtime set to anything
other than sandboxd is rejected by CreatePod: this node serves exactly one
runtime.
template / net / size are the operator’s pkg/scale selector keys
verbatim, so one contract spans the L2 claim gateway and this provider.
| Annotation | Direction | Meaning |
|---|---|---|
sandbox.cocoonstack.io/runtime |
in | Must be sandboxd (absent is treated as sandboxd) |
sandbox.cocoonstack.io/template |
in | sandboxd template axis. Required – CreatePod fails without it |
sandbox.cocoonstack.io/net |
in | Claim network axis; empty means the sandboxd default |
sandbox.cocoonstack.io/size |
in | Claim VM size axis; empty means the sandboxd default |
sandbox.cocoonstack.io/ttl-seconds |
in | Claim lease in seconds. Absent means 86400 — sandboxd clamps to its 24h maximum — because a pod’s sandbox lives until the pod is deleted, and sandboxd’s own default of five minutes is sized for ephemeral SDK claims. An explicit 0 still selects that sandboxd default. A non-integer or negative value fails the create |
sandbox.cocoonstack.io/claim-id |
out | Written back by the provider: the sandboxd claim id backing the Pod |
The release token is deliberately not exposed on the Pod – it stays in the node’s claims table, which is what keeps VM destruction an authorized, node-local operation.
The provider pushes a synthetic status; nothing in it is reported by a kubelet, because there is no container runtime on this node:
| Field | Value |
|---|---|
status.phase |
Running once claimed, Pending while the Pod is tracked without a claim |
status.podIP / podIPs / hostIP |
Host part of sandboxd’s owner_addr – the microVM’s address |
status.conditions |
Initialized, Ready, PodScheduled all True |
status.containerStatuses[] |
One ready, running entry per spec.containers entry, with imageID = sandboxd://<claim id> |
Container specs are otherwise not interpreted: the workload runs inside the
microVM, not as containers on this node. kubectl logs, exec, attach, and
port-forward against these Pods are not served – interactive access goes
through the sandbox SDK and preview URLs.
| Situation | Behaviour |
|---|---|
No warm capacity (sandboxd 429, or a redirect to warm peers) |
CreatePod fails typed; the Pod stays Pending and the operator’s L1 path handles fallback. This provider never queues or retries into the node |
Missing or invalid template / ttl-seconds |
CreatePod fails; no claim is made |
Pod deleted, owner Sandbox still alive |
The claim is preserved; the VM keeps running |
| A replacement Pod with the same namespace/name | Adopts the preserved claim in place – same VM, no second claim |
| Pod update | Recorded only; sandbox Pods are immutable at the runtime level |
Owner Sandbox deleted |
Release authorized; the microVM is destroyed |
The full decision table for the last two rows is in Architecture.
sandboxd persists a claim before writing the HTTP response, so a transport
failure can leave a live sandbox whose token nobody received. sandboxd is on
loopback, making that a process-death-class rarity, and the stray is bounded
by its lease — or by the archive retention policy, where an archive-enabled
pool may keep it longer — and logged by the orphan scan — so there is deliberately no
recovery machinery for it. The claim still carries the pod key as claim_ref,
which is what lets an operator trace such a stray to the Pod it was for.
Nothing in the stack renews a lease — the e2b keepalive is record-keeping
only — so sandboxd’s reaper destroys the microVM at the claim’s deadline. The
provider records the deadline returned with each claim and, once it passes,
pushes the Pod Failed with reason SandboxLeaseExpired instead of letting a
dead workload read as Running — pushed, because virtual-kubelet never polls an
asynchronous provider, so only a published status exists. The release credential stays valid either way.
Pods beyond 24 hours are outside the current contract: leases are fixed at
claim time by design, and that boundary is reported honestly rather than
papered over.