Skip to main content

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 namespace
  • image (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 with K7_REGISTRY_ALLOWLIST)
  • backend (string | null): kata-firecracker-devmapper (kfd), kata-qemu-longhorn (kql), k7d, or k7d-fc. Auto-detected when omitted
  • docker (boolean, default false): Run a pinned dockerd inside the sandbox with overlay2 on a block graph disk. On k7d / k7d-fc it is an in-guest agent service and the sandbox stays forkable; on kfd / kql a privileged docker-vehicle container is injected. See Docker in a sandbox
  • docker_disk (string | null, default 20Gi): Graph disk size for docker: true
  • sidecar (string | null): Deprecated — "docker" is an alias of docker: true
  • root_disk_size (string, default 10Gi): Longhorn root PVC size (kql only)
  • namespace (string, default default): Kubernetes namespace
  • env_file (string | null): Path (on API host) to .env file to inject as Secret
  • before_script (string, default empty): Shell commands to run before the container is marked Ready
  • entrypoint / cmd (string[] | null): Override image ENTRYPOINT / CMD
  • limits (object): Resource limits/requests; keys supported: cpu, memory, ephemeral-storage
  • egress_whitelist (string[] | [] | null): omit / null = open; [] = block-all; listed CIDRs or FQDNs = allowlist. See Networking
  • ingress_ports (int[] | null): TCP ports to open for inbound traffic. Omit / [] = deny all ingress (default)
  • ingress_from (string[] | null): Who may reach ingress_ports — sandbox:<name>, namespace:<ns>, or cidr:<cidr>. Omitted with ports set = sandboxes in the same namespace, never the world. Any other entry, or ingress_from without ingress_ports, is 400
  • expose_ports (int[] | null): Publish these ports outside the cluster through a NodePort Service {name}-expose (externalTrafficPolicy: Local). Each must also appear in ingress_ports; otherwise 400. Allocated NodePorts are returned as node_ports on list / get
  • volumes (object[] | null): hostPath / PVC volume entries. Refused with 400 on k7d-fc
  • pod_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 is ALL
  • cap_add (string[] | null): List of capabilities to add back

Responses

  • 201 Created with 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 BadRequest when validation fails (invalid limits, bad env file, already exists, etc.)

List sandboxes

Endpoint: GET /api/v1/sandboxes
string
default:"default"
Namespace
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}
string
required
Sandbox name
string
default:"default"
Namespace

Delete sandbox

Endpoint: DELETE /api/v1/sandboxes/{name}
string
required
Sandbox name
string
default:"default"
Namespace

Delete all sandboxes

Endpoint: DELETE /api/v1/sandboxes
string
default:"default"
Namespace
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.
string
required
Sandbox name
string
default:"default"
Namespace
string
default:"sandbox"
Container name inside the pod
int
default:"200"
Number of trailing lines to return
int
default:"0"
Only return entries newer than N seconds (0 = no filter)
Responses:
  • 200 OK with {"data": {"logs": "..."}} on success.
  • 404 NotFound when 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). --snapshot / the snapshot body field is kata-qemu-longhorn-only.
string
required
Sandbox name
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, restarts the vCPU loop — in-memory state survives.
string
required
Sandbox name
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). 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. The request blocks until the new sandbox is usable.
string
required
Source sandbox name
Body:
new_name is required. snapshot is an optional name for the fork-time intermediate snapshot; if omitted, k7 picks one.
Responses:
  • 201 Created with Location: /api/v1/sandboxes/{new_name}?namespace={namespace}
  • 400 BadRequest when new_name is missing, or the source backend doesn’t support fork
  • 404 NotFound when the source Deployment or its root PVC is missing
  • 409 Conflict when new_name already exists in the namespace

See also