Agent Sandbox

Envd Sandbox

This example demonstrates running E2B’s envd daemon as the container entrypoint inside an agent-sandbox, providing an E2B-compatible REST and gRPC API for filesystem operations, process execution, and metrics.

This example demonstrates running envd — E2B’s in-sandbox daemon — as the container entrypoint inside an agent-sandbox. envd exposes an E2B-compatible REST and gRPC API on port 49983, providing filesystem operations, process execution, environment management, and metrics.

Overview

envd is the data-plane daemon that runs inside every E2B sandbox. It provides:

  • Filesystem API — upload, download, list, stat, watch files
  • Process API — start, connect, stream I/O (with PTY support)
  • REST endpoints/health, /metrics, /init, /envs, /freeze, /unfreeze
  • Port forwarding — auto-discovers and forwards localhost ports

This example builds envd from the upstream e2b-dev/infra source and runs it in a standard Linux container with the --isnotfc flag (skipping Firecracker MMDS polling). No KVM or kata-deploy is required.

The kvcache-ai/AgentENV project uses envd in a similar way, packaging it into an ext4 tools drive attached to Firecracker microVMs for their AI agent RL training platform.

Architecture

  Client SDK (Python / Go / TS / curl)
        
          HTTP / gRPC (port 49983)
          ─── kubectl port-forward ───┐
                                       
  ┌─────────────────────────────────────┐
    envd Pod (standard Linux container)
                                       
    REST API:                          
      GET  /health                     
      GET  /metrics                    
      POST /init                       
      GET  /envs                       
      GET  /files   (download)         
      POST /files   (upload)           
                                       
    gRPC (ConnectRPC):                 
      filesystem.{Stat,ListDir,...}    
      process.{Start,Connect,...}      
                                       
    Port Scanner + Forwarder           
  └─────────────────────────────────────┘
        
        
  agent-sandbox controller

Prerequisites

  1. Kubernetes cluster with agent-sandbox controller installed (quickstart)
  2. kubectl configured to access the cluster
  3. Docker (or other OCI builder) to build the envd image

Step 1 — Build and push the envd image

cd examples/envd-sandbox

# Build (default uses a pinned envd commit; override with --build-arg ENVD_VERSION=<ref>)
export IMAGE=<your-registry>/envd-sandbox:latest
docker build -t ${IMAGE} .

# Push (for Kind, use: kind load docker-image ${IMAGE})
docker push ${IMAGE}

Step 2 — Create the Sandbox

envsubst < sandbox-envd.yaml | kubectl apply -f -
kubectl get sandbox envd-example

Step 3 — Verify the pod is running

kubectl get pod -l sandbox=envd
kubectl logs -l sandbox=envd
# Should show envd startup banner and "listening on :49983"

Step 4 — Run the verification scripts

First, set up port-forwarding:

POD=$(kubectl get pod -l sandbox=envd -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward pod/$POD 49983:49983 &
PF_PID=$!
trap 'kill $PF_PID 2>/dev/null' EXIT

export SANDBOX_BASE_URL=http://127.0.0.1:49983

# Wait for readiness
for i in $(seq 1 30); do
  curl -sf ${SANDBOX_BASE_URL}/health >/dev/null 2>&1 && break
  sleep 0.2
done

Then run any of the four client scripts:

Python

pip install -r requirements.txt
python test_client.py

Shell (curl + jq)

chmod +x test_client.sh
./test_client.sh

Go

go run test_client.go

TypeScript

npx tsx test_client.ts

All four scripts test the same operations:

  1. healthGET /health → 204
  2. initPOST /init → 204 (initializes sandbox with defaultUser: user for subprocess execution)
  3. files — upload + download round-trip via POST/GET /files
  4. metricsGET /metrics → JSON with system stats (ts, cpu_count, mem_total, etc.)

Security Considerations

WARNING: envd in --isnotfc mode runs without authentication. Do NOT expose port 49983 to a public network.

Security model (following e2b-dev design):

  • envd runs as root: Required for PTY allocation, process management, and cgroup operations.
  • User code runs as non-root: The /init request sets defaultUser: "user" (uid 1000), so all user-spawned processes execute as an unprivileged user. This limits blast radius if user code is malicious.

The included sandbox-envd.yaml applies a default-deny ingress NetworkPolicy that restricts access to port 49983 to pods labeled access: envd-client. This prevents unauthorized workloads in the cluster from reaching the envd API.

Production hardening checklist

  • NetworkPolicy: The default manifest includes a deny-all ingress policy. Adjust the podSelector to match your cluster’s access control model (e.g., allow only the sandbox-router pod).
  • Pin envd version: The Dockerfile pins ENVD_VERSION to a specific upstream commit. To update, override at build time:
    docker build --build-arg ENVD_VERSION=<commit-or-tag> -t ${IMAGE} .
    
  • Token authentication: To enable authenticated access, send an initial POST /init request with an accessToken field to bootstrap the daemon’s token. Subsequent requests must include the X-Access-Token header. Do not set E2B_ACCESS_TOKEN as an environment variable — the token is configured via the /init bootstrap flow.
  • Non-root subprocesses: The Dockerfile creates a user (uid 1000) and the test clients set defaultUser: "user" in /init. All user code runs as this unprivileged user by default.
  • Ephemeral storage: envd writes to the container’s rootfs; pod restart wipes all state.

Troubleshooting

Symptom Cause Fix
Pod stuck in CrashLoopBackOff envd cannot reach MMDS (missing --isnotfc) Verify the Dockerfile CMD includes --isnotfc
failed to create cgroup in logs cgroup v2 not available in container Ensure --no-cgroups flag is set in CMD
connection refused on port 49983 envd not yet ready Wait for readiness probe; check kubectl logs
File upload returns 500 Path resolution issue File paths resolve relative to /home/user; use relative paths or absolute paths under /home/user/
Port-forward drops Pod restarted Re-run kubectl port-forward

Cleanup

kubectl delete -f <(envsubst < sandbox-envd.yaml)
# Or individually:
# kubectl delete sandbox envd-example
# kubectl delete networkpolicy envd-deny-ingress