Create sandbox#
Endpoint: POST /api/v1/sandboxes
Body example:
Body example with egress whitelist (safe pattern: proxy IP only):
Request body schema#
Fields accepted in the JSON body when creating a sandbox:
name(string, required): Unique sandbox name in the namespaceimage(string, required): Container image, e.g.alpine:latest. Registry host must be public and allowlisted (registry-1.docker.io,ghcr.io,quay.io,public.ecr.aws; extend withK7_REGISTRY_ALLOWLIST)backend(string | null):kata-firecracker-devmapper(kfd),kata-qemu-longhorn(kql),k7d, ork7d-fc. Auto-detected when omitteddocker(boolean, defaultfalse): Run a pinneddockerdinside the sandbox withoverlay2on a block graph disk. Onk7d/k7d-fcit is an in-guest agent service and the sandbox stays forkable; onkfd/kqla privilegeddocker-vehiclecontainer is injected. See Docker in a sandboxdocker_disk(string | null, default20Gi): Graph disk size fordocker: truesidecar(string | null): Deprecated —"docker"is an alias ofdocker: trueroot_disk_size(string): Longhorn root PVC size (kqlonly). Default: the tenant’sdefaultSize.disk, else10Ginode_name(string | null): Kubernetes node to pin the sandbox to (k7 nodes list). A key scoped to one node auto-pins; a key scoped to several requires it; a node outside the key’s scope or the tenant’sspec.nodesis403network_limits(object | null):{"egressBps": "100M", "ingressBps": "100M"}in bits/s, enforced per VM onkfd(Firecracker token buckets) andkql(tcon the tap). May only lower a tenant cap. Onk7d/k7d-fca configured cap is400 LimitNotEnforceablerather than running unlimitednamespace(string, defaultdefault): Kubernetes namespaceenv_file(string | null): Path (on API host) to.envfile to inject as Secretbefore_script(string, default empty): Shell commands to run before the container is marked Readyentrypoint/cmd(string[] | null): Override image ENTRYPOINT / CMDlimits(object): Resource limits/requests; keys supported:cpu,memory,ephemeral-storage. Inproductionmode CPU and memory are required, filled from the tenant’sdefaultSizewhen omitted;limits.maxSandboxSizeon the tenant caps them (400above it)egress_whitelist(string[] | [] | null): omit /null= open;[]= block-all; listed CIDRs or FQDNs = allowlist. See Networkingingress_ports(int[] | null): TCP ports to open for inbound traffic. Omit /[]= deny all ingress (default)ingress_from(string[] | null): Who may reachingress_ports—sandbox:<name>,namespace:<ns>, orcidr:<cidr>. Omitted with ports set = sandboxes in the same namespace, never the world. Any other entry, oringress_fromwithoutingress_ports, is400expose_ports(int[] | null): Publish these ports outside the cluster through aNodePortService{name}-expose(externalTrafficPolicy: Local). Each must also appear iningress_ports; otherwise400. Allocated NodePorts are returned asnode_portson list / getvolumes(object[] | null): hostPath / PVC volume entries. Refused with400onk7d-fcpod_non_root(boolean, default false): Run pod as non-root (UID/GID/FSGroup 65532)container_non_root(boolean, default false): Run container as non-root (UID 65532)cap_drop(string[] | null): List of capabilities to drop; default policy isALLcap_add(string[] | null): List of capabilities to add back
Responses#
201 Createdwith Location header to the created resource:
When expose_ports is set the body also carries endpoints: [{ "port", "node_port", "url" }] — the resolved http://<node-ip>:<nodeport> for each exposed port.
400 BadRequestwhen validation fails (invalid limits, bad env file, already exists, a tenant limit the backend cannot enforce —LimitNotEnforceable, etc.)403 Forbiddenwhen the role does not allow creates (read-only,operator), the namespace is outside the caller’s tenant, or a tenant quota would be exceeded (QuotaExceeded, with dimension, limit, usage and request)503 Service Unavailablewhen the failure is the cluster’s, not the request’s: the registry host does not resolve (cluster DNS down), the quota Lease could not be taken within 5 s, or — with--metering-require-delivery— every candidate node’s usage spool is above its high-water mark (UsageSpoolFull)
List sandboxes#
Endpoint: GET /api/v1/sandboxes
Returns list of sandbox objects with fields: name, namespace, status, ready, restarts, age, image, backend, node, node_ports, error_message. node_ports lists the NodePorts allocated by expose_ports (empty when the sandbox is not exposed).
Get sandbox#
Endpoint: GET /api/v1/sandboxes/{name}
Delete sandbox#
Endpoint: DELETE /api/v1/sandboxes/{name}
Delete all sandboxes#
Endpoint: DELETE /api/v1/sandboxes
Deleting sandboxes is irreversible.
Get logs#
Endpoint: GET /api/v1/sandboxes/{name}/logs
Returns a snapshot of the pod’s container logs. Live follow streaming
isn’t shipped yet — use k7 --core logs --follow ... on a cluster node
for tail-and-follow until API streaming lands.
Responses:
200 OKwith{"data": {"logs": "..."}}on success.404 NotFoundwhen no pod matches the sandbox name in the namespace.
Pause sandbox#
Endpoint: POST /api/v1/sandboxes/{name}/pause
On kql, scales the sandbox Deployment to 0 and optionally creates a Longhorn
VolumeSnapshot of its root PVC (and of the <name>-docker-lh graph PVC for a
docker: true sandbox). On k7d / k7d-fc, freezes the live VM in place
(memory survives; the paused VM keeps its reserved vCPU/memory in the tenant quota and is metered as paused, 0 vCPU·s). --snapshot / the snapshot body field is kata-qemu-longhorn-only; on kfd a snapshot is rejected up front with 400 instead of scaling down first. A missing sandbox is 404.
Body (all keys optional):
When snapshot is set, the API snapshots the sandbox’s root PVC under
that name using the longhorn VolumeSnapshotClass. The PVC name is
derived server-side (<sandbox>-root-lh for kata-qemu-longhorn) — there is
no client-side flag for it. Without snapshot the endpoint only scales
to 0 without taking a snapshot.
Resume sandbox#
Endpoint: POST /api/v1/sandboxes/{name}/resume
On kql, scales the Deployment back to 1 (PVC re-attaches). On k7d / k7d-fc, restarts the vCPU loop — in-memory state survives and exec works immediately (Firecracker v1.16.2).
Body:
Fork sandbox#
Endpoint: POST /api/v1/sandboxes/{name}/fork
Clones a sandbox into a new name. kql: disk-only clone from a
VolumeSnapshot of the source’s root PVC (~45 s single-node; longer with
replicas ≥ 2). k7d / k7d-fc: warm CoW fork of disk and memory (~2–4 s to a
Ready pod; child inherits running processes, including a live docker: true
engine; with k7d 0.9.0+ the child also keeps working network). kfd: returns 400 (also for docker: true on any backend whose
graph disk cannot be cloned). The fork inherits the source’s egress and
ingress policy but not its expose_ports Service, stays in the source’s
namespace, lands on the source’s node (k7d) and is checked against the
tenant’s quotas like a create. The request blocks until the new sandbox is
usable; on kql the temporary snapshot is deleted only after Longhorn reports
the clone completed, so a fork never comes up with a half-copied disk.
Body:
new_name is required. snapshot is an optional name for the
fork-time intermediate snapshot; if omitted, k7 picks one.
Responses:
201 CreatedwithLocation: /api/v1/sandboxes/{new_name}?namespace={namespace}400 BadRequestwhennew_nameis missing, or the source backend doesn’t support fork404 NotFoundwhen the source Deployment or its root PVC is missing409 Conflictwhennew_namealready exists in the namespace403 QuotaExceededwhen the fork would exceed the tenant’ssandboxes,vcpu,memory,diskorsnapshotStoragequota
