#OpenClaw
Run OpenClaw inside a Sandbox0 sandbox when you want the OpenClaw gateway, workspace, logs, and session state to live behind Sandbox0 runtime controls.
The builtin openclaw template uses the upstream OpenClaw image and declares /home/node/.openclaw as the persistent mount point.
Create The Sandbox#
Create a SandboxVolume, claim the builtin openclaw template, mount the volume, and expose the OpenClaw gateway as a cmd Sandbox Service. The example leaves ttl and hard_ttl unset.
Set two tokens before running the SDK examples:
bashexport OPENCLAW_GATEWAY_TOKEN="$(openssl rand -hex 32)" export OPENCLAW_ROUTE_TOKEN="$(openssl rand -hex 32)"
OPENCLAW_ROUTE_TOKEN is checked by the Sandbox0 public route before traffic reaches OpenClaw. OPENCLAW_GATEWAY_TOKEN is passed to OpenClaw for its own gateway auth.
gopackage main import ( "context" "crypto/sha256" "encoding/hex" "fmt" "log" "os" sandbox0 "github.com/sandbox0-ai/sdk-go" "github.com/sandbox0-ai/sdk-go/pkg/apispec" ) func main() { ctx := context.Background() opts := []sandbox0.Option{sandbox0.WithToken(mustEnv("SANDBOX0_TOKEN"))} if baseURL := os.Getenv("SANDBOX0_BASE_URL"); baseURL != "" { opts = append(opts, sandbox0.WithBaseURL(baseURL)) } client, err := sandbox0.NewClient(opts...) if err != nil { log.Fatal(err) } routeHash := sha256.Sum256([]byte(mustEnv("OPENCLAW_ROUTE_TOKEN"))) volume, err := client.CreateVolume(ctx, apispec.CreateSandboxVolumeRequest{ AccessMode: apispec.NewOptVolumeAccessMode(apispec.VolumeAccessModeRWO), DefaultPosixUID: apispec.NewOptInt64(1000), DefaultPosixGid: apispec.NewOptInt64(1000), }) if err != nil { log.Fatal(err) } sandbox, err := client.ClaimSandbox(ctx, "openclaw", sandbox0.WithSandboxBootstrapMount(volume.ID, "/home/node/.openclaw"), sandbox0.WithSandboxAutoResume(true), sandbox0.WithSandboxEnvVars(map[string]string{ "OPENCLAW_GATEWAY_TOKEN": mustEnv("OPENCLAW_GATEWAY_TOKEN"), }), sandbox0.WithSandboxServices([]apispec.SandboxAppService{ { ID: "gateway", Port: apispec.NewOptInt32(18789), Runtime: apispec.NewOptSandboxAppServiceRuntime(apispec.SandboxAppServiceRuntime{ Type: apispec.SandboxAppServiceRuntimeTypeCmd, Command: []string{ "sh", "-lc", "exec openclaw gateway run --allow-unconfigured --auth token --token \"$OPENCLAW_GATEWAY_TOKEN\" --bind lan --port \"$SANDBOX0_SERVICE_PORT\"", }, }), HealthCheck: apispec.NewOptSandboxAppServiceHealth(apispec.SandboxAppServiceHealth{ Path: apispec.NewOptString("/health"), }), Ingress: apispec.SandboxAppServiceIngress{ Public: true, Routes: []apispec.SandboxAppServiceRoute{ { ID: "gateway", PathPrefix: apispec.NewOptString("/"), Methods: []string{"GET", "POST"}, TimeoutSeconds: apispec.NewOptInt32(60), Resume: true, Auth: apispec.NewOptSandboxAppServiceRouteAuth(apispec.SandboxAppServiceRouteAuth{ Mode: apispec.SandboxAppServiceRouteAuthModeBearer, BearerTokenSHA256: apispec.NewOptString(hex.EncodeToString(routeHash[:])), }), }, }, }, }, }), ) if err != nil { log.Fatal(err) } services, err := sandbox.GetServices(ctx) if err != nil { log.Fatal(err) } for _, service := range services.Services { if service.ID == "gateway" { fmt.Printf("SANDBOX_ID=%s\n", sandbox.ID) fmt.Printf("OPENCLAW_PUBLIC_URL=%s\n", service.PublicURL.Or("")) } } } func mustEnv(key string) string { value := os.Getenv(key) if value == "" { log.Fatalf("%s is required", key) } return value }
Use The Public URL#
Call the gateway through the returned OPENCLAW_PUBLIC_URL:
bashcurl -fsS "$OPENCLAW_PUBLIC_URL/health" \ -H "authorization: Bearer $OPENCLAW_ROUTE_TOKEN"
For OpenAI-compatible clients, point the client at the same base URL and provide the OpenClaw gateway token according to the OpenClaw gateway contract.
Pause And Resume#
Manual pause frees the runtime pod. A later public request can resume the sandbox because the sandbox has auto_resume: true and the route has resume: true.
go_, err := client.PauseSandbox(ctx, sandbox.ID) if err != nil { log.Fatal(err) } _, err = client.ResumeSandbox(ctx, sandbox.ID) if err != nil { log.Fatal(err) }
OpenClaw state under /home/node/.openclaw remains on the mounted volume. Running processes, sockets, memory, and in-flight requests are not preserved across pause/resume.
Outbound channel connections do not wake a paused sandbox by themselves. If OpenClaw listens to Telegram, Discord, or another external channel through long polling or WebSockets, put the webhook receiver in an always-on control plane that calls the Sandbox0 service URL, or keep that sandbox running.
Operator Notes#
The builtin template is configurable through Sandbox0Infra.spec.builtinTemplates. Remove templateId: openclaw from that list to delete the operator-managed public template, or replace its image, pool, or full spec to pin a reviewed OpenClaw image.
Next Steps#
Hermes
Run the Hermes gateway with a persistent /opt/data volume.
Sandbox Services
Tune route auth, timeouts, rate limits, and auto-resume.