Run Kubernetes with k3s
Create a disposable Kubernetes cluster for agents, CI, previews, and integration testing.
The e2b/k3s template turns an E2B Sandbox into a preconfigured, single-node k3s cluster. kubectl, cluster DNS, networking, storage, and the default k8s components are configured when the sandbox starts.
Use it when an agent or CI job needs a single-node Kubernetes cluster without provisioning or sharing a permanent cluster.
The k3s template is in preview and is not yet battle-tested across every Kubernetes workload. It is intended for ephemeral development and testing workflows, not as a highly available environment for hosting production traffic.
Quickstart
Create and connect to a k3s sandbox from the CLI:
e2b sbx create e2b/k3skubectl is already configured. On a fresh or resumed sandbox, wait for the node and cluster DNS objects to appear and become ready before deploying:
timeout 120s sh -c 'until kubectl get node -o name | grep -q .; do sleep 1; done'
kubectl wait --for=condition=Ready node --all --timeout=120s
timeout 120s sh -c 'until kubectl get pod --namespace=kube-system --selector=k8s-app=kube-dns -o name | grep -q .; do sleep 1; done'
kubectl wait --for=condition=Ready pod \
--namespace=kube-system \
--selector=k8s-app=kube-dns \
--timeout=120s
kubectl get nodes
kubectl get pods --all-namespacesYou can also create the sandbox with the standard E2B SDK:
import { Sandbox } from 'e2b'
// One timeout for the sandbox and for every command run inside it.
const TIMEOUT_MS = 15 * 60_000
const sandbox = await Sandbox.create('e2b/k3s', {
timeoutMs: TIMEOUT_MS,
})
try {
await sandbox.commands.run(
`
set -euo pipefail
timeout 120s sh -c 'until kubectl get node -o name | grep -q .; do sleep 1; done'
kubectl wait --for=condition=Ready node --all --timeout=120s
timeout 120s sh -c 'until kubectl get pod --namespace=kube-system --selector=k8s-app=kube-dns -o name | grep -q .; do sleep 1; done'
kubectl wait --for=condition=Ready pod \
--namespace=kube-system \
--selector=k8s-app=kube-dns \
--timeout=120s
`,
{ timeoutMs: TIMEOUT_MS },
)
const nodes = await sandbox.commands.run('kubectl get nodes -o wide', {
timeoutMs: TIMEOUT_MS,
})
console.log(nodes.stdout)
} finally {
await sandbox.kill()
}Deploy and preview an application
This example creates an nginx Deployment and Service, forwards the Service to a sandbox port, and prints an E2B URL that you can open in a browser.
Every command that has to stay alive for the whole session uses the same timeout as the sandbox, including the background port-forward. A command started with a shorter timeout is terminated when that timeout expires, which would close the forwarded port while the sandbox is still running.
import { Sandbox } from 'e2b'
// One timeout for the sandbox and for every command run inside it,
// including the background port-forward that serves the preview URL.
const TIMEOUT_MS = 15 * 60_000
const sandbox = await Sandbox.create('e2b/k3s', {
timeoutMs: TIMEOUT_MS,
})
await sandbox.commands.run(
`
set -euo pipefail
timeout 120s sh -c 'until kubectl get node -o name | grep -q .; do sleep 1; done'
kubectl wait --for=condition=Ready node --all --timeout=120s
timeout 120s sh -c 'until kubectl get pod --namespace=kube-system --selector=k8s-app=kube-dns -o name | grep -q .; do sleep 1; done'
kubectl wait --for=condition=Ready pod \
--namespace=kube-system \
--selector=k8s-app=kube-dns \
--timeout=120s
kubectl create deployment web --image=nginx:alpine
kubectl scale deployment web --replicas=2
kubectl expose deployment web --port=80
kubectl rollout status deployment/web --timeout=180s
`,
{ timeoutMs: TIMEOUT_MS },
)
await sandbox.commands.run(
'kubectl port-forward --address=0.0.0.0 service/web 8080:80',
{ background: true, timeoutMs: TIMEOUT_MS },
)
await sandbox.commands.run(
'until curl -fsS http://127.0.0.1:8080 >/dev/null; do sleep 1; done',
{ timeoutMs: TIMEOUT_MS },
)
const url = `https://${sandbox.getHost(8080)}`
console.log(`Open the application: ${url}`)
console.log(`Stop it with: e2b sandbox kill ${sandbox.sandboxId}`)The script keeps running while the port-forward is open, and the sandbox stays available for up to 15 minutes so you can open the URL. Stop the script with Ctrl+C when you are done watching it, and remove the sandbox with the command it printed:
e2b sandbox kill <sandbox-id>Stopping the script on its own does not delete the sandbox: it keeps serving the URL until you kill it or the 15-minute timeout expires.
To verify in-cluster DNS and ClusterIP routing, run a client pod against the Service:
kubectl run cluster-check \
--rm -i \
--restart=Never \
--image=curlimages/curl:latest \
-- curl -fsS http://webCheck k3s status and logs
k3s runs as a systemd service with automatic restart on crash. Use sudo: the sandbox has no user D-Bus session, so unprivileged systemctl and journalctl cannot connect.
sudo systemctl status k3s # unit state
sudo journalctl -u k3s -f # server logs
kubectl get --raw='/readyz?verbose' | tail -5
kubectl get pods --all-namespacesServer flags live in /etc/rancher/k3s/config.yaml; edit the file and sudo systemctl restart k3s to apply. Workload logs work as usual through kubectl logs.
Where to use it
- Give a coding agent an isolated cluster for a repository that expects Kubernetes.
- Validate Helm charts, operators, CRDs, manifests, Services, and DNS in CI.
- Create a cluster per pull request for integration tests or browser previews.
- Reproduce Kubernetes issues without provisioning EKS, GKE, or AKS.
- Test untrusted workloads without sharing a staging cluster.
The useful production pattern is cluster per task: create a sandbox, deploy and test the workload, collect the results, and remove the sandbox. Promote the same images, charts, and manifests to your managed production cluster after validation.
Create an isolated k3s sandbox
The agent, CI job, or preview service gets its own Firecracker microVM and Kubernetes control plane.
Deploy the production artifacts
Apply the same container images, Helm charts, CRDs, or manifests that will be promoted later.
Test the complete workload
Exercise Pods, Services, DNS, storage, and browser-visible endpoints without affecting a shared cluster.
Promote and clean up
Send validated artifacts to the production cluster, save any required test output, and kill the sandbox.
What is tested and supported
The template runs stock k3s with Flannel’s default VXLAN backend. These workflows are validated end-to-end on every release:
- Deployments, Services with load balancing across replicas (kube-proxy iptables and nftables modes), and session affinity.
- Ingress (Traefik), NodePort, LoadBalancer (ServiceLB), and
hostPort. - NetworkPolicy, both ingress and egress, with the built-in controller.
- StatefulSets with local-path PersistentVolumeClaims, Jobs, CronJobs, and HPA with metrics-server.
- Alternative CNIs that use VXLAN, such as Calico with
CALICO_IPV4POOL_VXLAN=Always. - Sandbox pause and resume: the cluster comes back immediately, the clock is corrected, and CronJobs keep their schedule.
Known limitations
- The template runs one Kubernetes node in one sandbox. It does not provide high availability, and multi-node clusters across sandboxes are not supported.
- A sandbox is disposable. Do not treat its local Kubernetes storage as a durable system of record.
- Cilium and other eBPF dataplanes do not work: the sandbox kernel has no BPF JIT.
- Service meshes with iptables interception (Istio, Linkerd) do not work: the kernel lacks the
ownerandTPROXYnetfilter extensions their sidecars need. - Calico’s default IPIP mode is not supported; use its VXLAN mode. GRE tunnels and SCTP Services are also unavailable.
- IPVS kube-proxy mode and nested-virtualization runtimes such as Kata Containers or KubeVirt are not supported.
kubectl port-forward service/<name>connects to one Pod selected when the forward starts; use in-cluster requests to exercise load balancing across replicas.
For production application hosting, use a managed or otherwise highly available Kubernetes cluster with durable storage, backups, monitoring, and load balancing. Use E2B k3s to develop and validate what you deploy there.