Skip to documentation
API + guides

01Documentation

Docker in Sandbox

Docker in Sandbox lets a Sandbox0 Cloud sandbox run a Docker daemon inside the sandbox. Use it when a coding agent or test workflow expects Docker commands such as docker run or docker build.

Typical uses:

  • Run Redis, PostgreSQL, or other service containers beside application tests.
  • Build a throwaway image from code generated inside the sandbox.
  • Run tools that require docker build, docker run, or docker compose style workflows.

/var/lib/docker uses ephemeral runtime storage. Images, containers, layers, and Docker-managed volumes are discarded when the sandbox runtime is replaced or deleted. Store source code and durable outputs elsewhere in the sandbox rootfs so pause, resume, and named rootfs snapshots can retain them.

The built-in default and coding-agent templates provide Docker binaries and writable Docker state, but they do not start dockerd before the sandbox is claimed. The examples below use default; claim coding-agent instead when the same sandbox also needs the bundled coding agents and Playwright CLI.

Docker should run inside the sandbox boundary. Do not mount a host Docker socket into an agent sandbox just to make Docker commands work.


Cloud Usage Pattern#

For Sandbox0 Cloud, use host networking for service containers:

  • Start a sandbox-local Docker daemon with Docker-managed bridge networking disabled.
  • Run service containers with --network=host.
  • Connect to those services from commands in the same sandbox at 127.0.0.1:<port>.

Start Docker once per running sandbox:

bash
if ! docker info >/dev/null 2>&1; then rm -f /var/run/docker.sock /run/docker.sock /var/run/docker.pid /usr/bin/dockerd \ --host=unix:///var/run/docker.sock \ --data-root=/var/lib/docker \ --exec-root=/var/run/docker \ --storage-driver=vfs \ --iptables=false \ --ip6tables=false \ --bridge=none \ --ip-forward=false \ --ip-masq=false \ >/tmp/sandbox0-dockerd.log 2>&1 & until docker info >/dev/null 2>&1; do sleep 1; done fi

Start Test Databases#

Claim a default sandbox, start Redis and PostgreSQL with Docker, then run tests against localhost from inside the sandbox.

go
package main import ( "context" "fmt" "log" "os" "strings" "time" sandbox0 "github.com/sandbox0-ai/sdk-go" ) func main() { ctx := context.Background() client, err := sandbox0.NewClient( sandbox0.WithToken(os.Getenv("SANDBOX0_TOKEN")), sandbox0.WithBaseURL(os.Getenv("SANDBOX0_BASE_URL")), ) if err != nil { log.Fatal(err) } sandbox, err := client.ClaimSandbox(ctx, "default", sandbox0.WithSandboxHardTTL(1800)) if err != nil { log.Fatal(err) } defer client.DeleteSandbox(ctx, sandbox.ID) if _, err := sandbox.Cmd(ctx, "rm -f /tmp/sandbox0-test-databases.ready"); err != nil { log.Fatal(err) } startScript := strings.Join([]string{ "set -e", "if ! docker info >/dev/null 2>&1; then", "rm -f /var/run/docker.sock /run/docker.sock /var/run/docker.pid", "/usr/bin/dockerd --host=unix:///var/run/docker.sock --data-root=/var/lib/docker --exec-root=/var/run/docker --storage-driver=vfs --iptables=false --ip6tables=false --bridge=none --ip-forward=false --ip-masq=false >/tmp/sandbox0-dockerd.log 2>&1 &", "until docker info >/dev/null 2>&1; do sleep 1; done", "fi", "rm -f /tmp/sandbox0-test-databases.ready", "docker rm -f test-redis test-postgres >/dev/null 2>&1 || true", "docker pull redis:7-alpine", "docker pull postgres:16-alpine", "docker run -d --name test-redis --network=host redis:7-alpine", "docker run -d --name test-postgres --network=host -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=app_test postgres:16-alpine", "until docker exec test-redis redis-cli ping | grep -q PONG; do sleep 1; done", "until docker exec test-postgres pg_isready -U postgres >/dev/null; do sleep 1; done", "touch /tmp/sandbox0-test-databases.ready", "tail -f /dev/null", }, "\n") start, err := sandbox.Cmd(ctx, "sh", sandbox0.WithCommand([]string{"/bin/sh", "-lc", startScript}), sandbox0.WithCmdWait(false), sandbox0.WithCmdTTL(1800), ) if err != nil { log.Fatal(err) } defer sandbox.DeleteContext(ctx, start.ContextID) readyCommand := "test -f /tmp/sandbox0-test-databases.ready && printf READY" deadline := time.Now().Add(5 * time.Minute) for { result, err := sandbox.Cmd(ctx, "sh", sandbox0.WithCommand([]string{"/bin/sh", "-lc", readyCommand}), ) if err != nil { log.Fatal(err) } if strings.Contains(result.OutputRaw, "READY") { break } if time.Now().After(deadline) { log.Fatal("timed out waiting for test databases") } time.Sleep(2 * time.Second) } fmt.Println("REDIS_URL=redis://127.0.0.1:6379") fmt.Println("DATABASE_URL=postgres://postgres:[email protected]:5432/app_test?sslmode=disable") }

Use those URLs from commands that run inside the same sandbox:

bash
REDIS_URL=redis://127.0.0.1:6379 \ DATABASE_URL='postgres://postgres:[email protected]:5432/app_test?sslmode=disable' \ go test ./...

Build And Run An Image#

Docker build cache also lives under /var/lib/docker, so this is best for temporary test images rather than durable artifacts.

go
script := strings.Join([]string{ "set -e", "if ! docker info >/dev/null 2>&1; then", "rm -f /var/run/docker.sock /run/docker.sock /var/run/docker.pid", "/usr/bin/dockerd --host=unix:///var/run/docker.sock --data-root=/var/lib/docker --exec-root=/var/run/docker --storage-driver=vfs --iptables=false --ip6tables=false --bridge=none --ip-forward=false --ip-masq=false >/tmp/sandbox0-dockerd.log 2>&1 &", "until docker info >/dev/null 2>&1; do sleep 1; done", "fi", "cat > main.go <<'EOF'", "package main", "import \"fmt\"", "func main() { fmt.Println(\"hello from docker in sandbox\") }", "EOF", "CGO_ENABLED=0 go build -o hello main.go", "cat > Dockerfile <<'EOF'", "FROM scratch", "COPY hello /hello", "ENTRYPOINT [\"/hello\"]", "EOF", "docker build -t sandbox0-docker-test .", "docker run --rm --network=none sandbox0-docker-test", }, "\n") result, err := sandbox.Cmd(ctx, "sh", sandbox0.WithCommand([]string{"/bin/sh", "-lc", script}), ) if err != nil { log.Fatal(err) } if !strings.Contains(result.OutputRaw, "hello from docker in sandbox") { log.Fatalf("docker build test failed:\n%s", result.OutputRaw) } fmt.Print(result.OutputRaw)

Nested Kubernetes And kind#

Nested Kubernetes tools such as kind and k3d need more than basic Docker command support. They create Kubernetes node containers and expect Docker to support cgroup namespaces plus host-like container networking. If the command fails with an error such as cgroup namespaces aren't enabled in the kernel, that sandbox cannot run kind reliably even if ordinary docker run and docker build commands work.

For projects that require kind, run that part of the workflow in a VM, a CI runner, or another environment with full Docker bridge networking and cgroup namespace support. See the kind known issues for the cgroup namespace requirement.


Operational Notes#

  • Containers launched by Docker are sandbox-local. In Sandbox0 Cloud, use --network=host for Redis, PostgreSQL, nginx, and similar service containers, then connect to 127.0.0.1 from commands inside the sandbox.
  • If you run against an environment where Docker port publishing works, -p hostPort:containerPort is also fine. The Cloud examples use --network=host because it avoids relying on Docker bridge/NAT.
  • Use Sandbox Services when a containerized HTTP service needs to be reachable from outside the sandbox.
  • Docker state does not survive runtime replacement. Keep source, generated files, or database dumps outside /var/lib/docker, then use a named rootfs snapshot or a custom template when they must survive sandbox cleanup.
  • Docker state uses the selected template's ephemeral storage limit. Hosted default sandboxes provide 500m CPU, 2Gi memory, and 8Gi ephemeral storage; coding-agent provides 1 CPU, 4Gi memory, and 16Gi ephemeral storage.
  • Pulling images can take longer than a single synchronous command request. Start long Docker setup commands asynchronously, then poll readiness with short commands.
  • kind and similar nested Kubernetes tools require cgroup namespace and networking support beyond ordinary Docker builds. Use a VM or CI runner when those kernel features are not available.
  • Long-running containers count against the sandbox resource limits. Stop containers when a test run finishes if the sandbox will stay alive.

Next Steps#