Back to Blog

HANDS-ON / GOCLAW BLOG

Run E2B sandbox tasks from a kind cluster

Run an E2B worker in a local kind cluster to create, execute, and clean up a sandbox while the E2B data plane remains hosted.

E2BkindKubernetesSandbox
01

Lab scope

kind runs Kubernetes nodes as containers and is designed for local development and CI. This lab places the E2B-calling worker, secret injection, Job lifecycle, and log observation inside kind; the sandbox itself is still created in E2B's managed environment.

This is not an E2B self-hosting guide. The official infrastructure uses Terraform, Packer, Nomad, PostgreSQL, object storage, and Firecracker orchestrators. The AWS architecture explicitly requires bare metal or nested virtualization support.

  • kind validates Kubernetes Jobs, configuration injection, SDK calls, failure handling, logs, and cleanup.
  • E2B validates sandbox creation, command execution, files, and lifecycle APIs.
  • It does not validate local Firecracker, nested virtualization, E2B's self-hosted control plane, or real capacity scheduling.
02

The lab flow

The lab follows one execution path: load a local image into kind, let a Kubernetes Job read the API key from a Secret, create a sandbox, execute a deterministic command, print the result, and clean up in a finally block.

  • Entry: Kubernetes Job.
  • Credential: injected only through a Secret, never baked into the image or Git.
  • Execution: E2B Python SDK create → commands.run → kill.
  • Evidence: Job logs, sandbox ID, stdout, exit code, and cleanup result.
03

The worker must clean up explicitly

Keep smoke tests deterministic. Do not begin by allowing a model to generate arbitrary shell. This worker runs a fixed command and records the sandbox ID, exit code, and stdout as structured evidence.

It calls kill in a finally block on both success and failure. Production still needs TTL reclamation and control-plane compensation instead of relying only on graceful client exit.

worker.pyREFERENCE · REVIEW BEFORE RUNNING
import json
from e2b import Sandbox

sandbox = Sandbox.create(
    timeout=300,
    metadata={"lab": "goclaw-kind", "purpose": "smoke"},
)

try:
    result = sandbox.commands.run(
        "python -c 'import platform; print(platform.platform())'"
    )
    print(json.dumps({
        "sandbox_id": sandbox.sandbox_id,
        "exit_code": result.exit_code,
        "stdout": result.stdout.strip(),
    }, ensure_ascii=False))
finally:
    sandbox.kill()
04

Build an immutable lab worker

The example constrains the SDK major version. A real repository should pin the exact verified version and record upgrade evidence. The worker runs as a non-root user, and the API key is injected only when the Pod starts.

DockerfileREFERENCE · REVIEW BEFORE RUNNING
FROM python:3.12-slim

WORKDIR /app
RUN pip install --no-cache-dir "e2b>=2.3,<3"
COPY worker.py /app/worker.py

USER 65532:65532
CMD ["python", "/app/worker.py"]
05

Represent one bounded invocation as a Job

backoffLimit is zero so authentication errors or provider failures do not cause accidental repeated creation. Kubernetes cleans up the Job after five minutes, but this does not replace sandbox kill or TTL.

Production should add NetworkPolicy, workload identity or an external secret manager, plus idempotency, budget, and audit fields enforced by the GoClaw control plane.

deploy/e2b-smoke-job.yamlREFERENCE · REVIEW BEFORE RUNNING
apiVersion: batch/v1
kind: Job
metadata:
  name: e2b-smoke
  namespace: agent-lab
spec:
  backoffLimit: 0
  ttlSecondsAfterFinished: 300
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: goclaw/e2b-kind-lab:0.1.0
          imagePullPolicy: IfNotPresent
          env:
            - name: E2B_API_KEY
              valueFrom:
                secretKeyRef:
                  name: e2b-credentials
                  key: E2B_API_KEY
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 250m
              memory: 256Mi
06

Wrap the lab in Make targets

Do not turn the tutorial into a one-off paste sequence. make lab-up, make lab-image, make lab-secret, make lab-run, make lab-logs, and make lab-down give developers and CI the same entry points.

kind officially supports loading local images into a named cluster. Avoid latest and use IfNotPresent so kubelet does not try to pull the image remotely.

MakefileREFERENCE · REVIEW BEFORE RUNNING
LAB_CLUSTER ?= goclaw-e2b
LAB_IMAGE ?= goclaw/e2b-kind-lab:0.1.0

.PHONY: lab-up lab-image lab-secret lab-run lab-logs lab-down

lab-up:
	kind create cluster --name $(LAB_CLUSTER) --wait 90s
	kubectl create namespace agent-lab --dry-run=client -o yaml | kubectl apply -f -

lab-image:
	docker build -t $(LAB_IMAGE) .
	kind load docker-image $(LAB_IMAGE) --name $(LAB_CLUSTER)

lab-secret:
	@test -n "$(E2B_API_KEY)" || (echo "E2B_API_KEY is required"; exit 1)
	kubectl -n agent-lab create secret generic e2b-credentials \
		--from-literal=E2B_API_KEY="$(E2B_API_KEY)" \
		--dry-run=client -o yaml | kubectl apply -f -

lab-run:
	kubectl -n agent-lab delete job e2b-smoke --ignore-not-found
	kubectl apply -f deploy/e2b-smoke-job.yaml
	kubectl -n agent-lab wait --for=condition=complete job/e2b-smoke --timeout=180s

lab-logs:
	kubectl -n agent-lab logs job/e2b-smoke

lab-down:
	kind delete cluster --name $(LAB_CLUSTER)
07

What counts as a passing experiment

Keep at least one structured record proving sandbox_id is present, exit_code is zero, stdout contains Linux kernel information, and the Job completed. Then confirm in the E2B dashboard or CLI that the sandbox is terminated.

  • Authentication failure must fail the Job without unbounded retries.
  • A non-zero command exit must still trigger kill.
  • Deleting the kind cluster does not automatically delete remote sandboxes.
  • No output may contain E2B_API_KEY.
S

Primary sources and further reading

Technical details in this article come from the official documentation and repositories below. Check the documentation for your version before implementation.