E2B Docker
Docker inside the sandbox — dockerd and the compose plugin, so a workload can build and run containers.
Source
config/samples/e2b-docker_sandboxtemplate.yaml on the develop
branch — the file this page is rendered from, and the one to apply.
Same envd runtime as e2b-basic_sandboxtemplate.yaml, but the Pod runs a container
engine of its own: dockerd starts in the background, and code inside the
sandbox can docker build, docker run and docker compose up. That is what
a workload needs when the task is "build this image", "bring up this compose
file" or "run a database".
kubectl apply -f config/samples/e2b-docker_sandboxtemplate.yaml
WARNING: privileged. A container engine needs more of the kernel than a
container is usually given, so this template runs the sandbox Pod privileged
(and starts dockerd inside it). A container escape from here reaches the
node. It is the right trade for a trusted workload, and the wrong one for
untrusted code. On a cluster with a microVM runtime class — kata, or anything
that can run a Pod in its own kernel — add runtimeClassName to the Pod spec
below and the same privileged dockerd is confined to a guest VM instead of
sharing the node's kernel. e2b-kata_sandboxtemplate.yaml is that template with
the field already set.
Every image is public: the container engine comes from Docker Hub's own dind image, and the runtime pieces from this project's GHCR packages.
apiVersion: agents.navix.sh/v1alpha1
kind: SandboxTemplate
metadata:
name: e2b-envd-docker
spec:
version: 0.0.1
description: E2B-compatible sandbox with Docker inside — dockerd and the compose plugin in the sandbox.
idleImage: ghcr.io/scitix/agent-sandbox-idle:0.0.10
runtimes:
- name: envd
port: 49983
protocol: TCP
description: E2B envd runtime
readinessProbe:
httpGet:
port: 49983
path: /health
template:
spec:
automountServiceAccountToken: false
enableServiceLinks: false
shareProcessNamespace: false
containers:
- name: sandbox
# A container engine, not a workload image: dockerd, the docker CLI
# and the compose plugin are all in this one.
image: docker:29-dind
imagePullPolicy: IfNotPresent
command:
- /mnt/agentbox/tini
- -g
- --
- /bin/sh
- -c
- |
# An idle Pod wears the idle image, which has no dockerd to start.
if [ "$AGENTBOX_IS_IDLE_IMAGE" = "true" ] || [ -f /etc/agentbox_is_idle_image ]; then
echo "[dind] idle Pod; waiting for a claim."
exec sleep infinity
fi
# Plain unix socket: the socket is inside this Pod, and nothing
# outside it needs to reach the daemon.
export DOCKER_TLS_CERTDIR=""
echo "[dind] starting dockerd"
dockerd --host=unix:///var/run/docker.sock >/var/log/dockerd.log 2>&1 &
# Best effort: the first `docker` call should not race the daemon.
# Not fatal — a failure here is visible in the log above.
for _ in $(seq 1 30); do
if docker version >/dev/null 2>&1; then
echo "[dind] dockerd is ready"
break
fi
sleep 1
done
# Hand the foreground to envd, which is what the E2B SDK talks to.
exec /bin/sh /mnt/agentbox/agentbox-entrypoint.sh
env:
- name: AGENTBOX_DIR
value: /mnt/agentbox
- name: PORT
value: "49999"
# Scrub the service environment Kubernetes injects, so a command
# inside the sandbox cannot see the cluster's addresses.
- name: KUBERNETES_SERVICE_HOST
- name: KUBERNETES_SERVICE_PORT
- name: KUBERNETES_SERVICE_PORT_HTTPS
- name: KUBERNETES_PORT
- name: KUBERNETES_PORT_443_TCP
- name: KUBERNETES_PORT_443_TCP_ADDR
- name: KUBERNETES_PORT_443_TCP_PORT
- name: KUBERNETES_PORT_443_TCP_PROTO
ports:
- containerPort: 49983
name: envd
protocol: TCP
- containerPort: 49999
name: app
protocol: TCP
# A container engine plus whatever it builds: size for both. As with
# the E2B template, a Pool's own sizing overrides this.
resources:
limits:
cpu: "2"
memory: 4Gi
requests:
cpu: "2"
memory: 4Gi
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
volumeMounts:
- name: shared-bin
mountPath: /mnt/agentbox
readOnly: true
# Node disk, not the container's own overlay root: overlay2 on top
# of overlayfs is something dockerd refuses to start with.
- name: docker-storage
mountPath: /var/lib/docker
initContainers:
- name: tini-injector
image: ghcr.io/scitix/agent-sandbox-tini:v0.19.0-static
imagePullPolicy: IfNotPresent
command: [sh, -c]
args:
- |
TARGET_DIR="/mnt/agentbox"
mkdir -p "$TARGET_DIR"
if [ -f "$TARGET_DIR/tini" ]; then
echo "[Init] tini already exists at $TARGET_DIR. Skipping copy."
else
cp /workspace/tini "$TARGET_DIR/tini"
chmod +x "$TARGET_DIR/tini"
echo "[Init] tini static injected successfully."
fi
volumeMounts:
- name: shared-bin
mountPath: /mnt/agentbox
- name: envd-injector
image: ghcr.io/scitix/agent-sandbox-envd:0.9.0-2
imagePullPolicy: IfNotPresent
command: [sh, -c]
args:
- |
TARGET_DIR="/mnt/agentbox"
mkdir -p "$TARGET_DIR"
if [ -f "$TARGET_DIR/envd" ] && [ -f "$TARGET_DIR/agentbox-entrypoint.sh" ]; then
echo "[Init] envd and scripts already exist in $TARGET_DIR. Skipping copy."
else
cp /workspace/envd "$TARGET_DIR/"
cp /workspace/agentbox-entrypoint.sh "$TARGET_DIR/"
chmod +x "$TARGET_DIR/envd" "$TARGET_DIR/agentbox-entrypoint.sh"
echo "[Init] envd & scripts successfully injected."
fi
volumeMounts:
- name: shared-bin
mountPath: /mnt/agentbox
volumes:
- name: shared-bin
emptyDir: {}
- name: docker-storage
emptyDir: {}kubectl apply -f config/samples/e2b-docker_sandboxtemplate.yaml