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.
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.
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.
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()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.
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"]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.
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: 256MiWrap 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.
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)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.
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.