Per-Developer Agent Sandboxes on GKE with Pulumi
Deploy kubernetes-sigs/agent-sandbox on a GKE cluster with a gVisor node pool, per-developer sandboxes, and Tailscale access
This example lives in the pulumi/examples repository. Check out just this directory to use it:
git clone --filter=blob:none --sparse https://github.com/pulumi/examples pulumi-examplesgit -C pulumi-examples sparse-checkout set gcp-ts-agent-sandboxcd pulumi-examples/gcp-ts-agent-sandboxA section-by-section walkthrough of this example is in the Pulumi blog post Kubernetes Agent Sandbox: what it is and how to deploy it with Pulumi.
This example deploys the kubernetes-sigs/agent-sandbox project onto a Google Kubernetes Engine (GKE) cluster and gives each developer a kernel-isolated, disposable coding-agent environment. It stands up:
- A GKE Standard cluster with a dedicated GKE Sandbox (gVisor) node pool, so every agent pod runs under the gVisor userspace kernel instead of sharing the host kernel.
- The Agent Sandbox controller and CRDs (
Sandbox, plus theSandboxTemplate/SandboxClaim/SandboxWarmPoolextensions), installed straight from the upstream release manifests. - One
Sandboxper developer, each with its own persistent workspace, its own credentials, and a default-deny egressNetworkPolicythat keeps the agent off the node metadata server and the cluster’s private ranges. - A Tailscale operator and per-sandbox
Ingress, so each box gets a private MagicDNS name and a real certificate that only its owner can reach — no public IP, no load balancer.
The tailnet ACL and the sandboxes are generated from the same developers list, so “who may open
which box” cannot drift from “which boxes exist.”
Note: Agent Sandbox is pre-1.0 (SIG Apps). Pin
agentSandboxVersionto a real release tag.
Prerequisites#
- Install the Pulumi CLI.
- A Google Cloud account with the
gcloudCLI on your path (part of the GCP SDK), and Pulumi connected to GCP. Use a throwaway project — these sandboxes are meant to be wrecked. - The
gke-gcloud-auth-plugininstalled, so the generated kubeconfig can authenticate to the cluster. - A Tailscale tailnet with two OAuth clients: one for the operator
(scopes:
devices:core,auth_keys), and one scoped to editing the ACL (scope:policy_file). See Tailscale OAuth clients. - A Claude Code OAuth credential blob to run inside the sandboxes (
~/.claude/.credentials.jsonand~/.claude.jsonfrom a machine where you’ve logged into Claude Code).
Warning — this example takes over your tailnet ACL. It manages the tailnet’s access-control policy as a Pulumi resource:
pulumi upoverwrites your existing tailnet ACL with a policy generated from thedeveloperslist, andpulumi destroyresets the ACL to Tailscale’s default. Run it against a tailnet you’re willing to have rewritten (a personal or test tailnet), not one whose ACL you rely on. See theoverwriteExistingContent/resetAclOnDestroysettings intailscale.ts.
Running the Example#
Clone the repo, cd gcp-ts-agent-sandbox, and install dependencies:
npm install-
Create a new stack:
Terminal window pulumi stack init dev -
Set the GCP project and zone:
Terminal window pulumi config set gcp:project [your-gcp-project]pulumi config set gcp:zone us-central1-a -
Set the developer list — one sandbox is created per entry:
Terminal window pulumi config set --path 'developers[0].name' adapulumi config set --path 'developers[0].email' ada@example.com -
Set the Tailscale OAuth clients (both clients, as secrets):
Terminal window pulumi config set --secret tailscaleOauthClientId [operator-client-id]pulumi config set --secret tailscaleOauthClientSecret [operator-client-secret]pulumi config set --secret tailscaleAclClientId [acl-client-id]pulumi config set --secret tailscaleAclClientSecret [acl-client-secret] -
Set the Claude Code credentials (as secrets):
Terminal window pulumi config set --secret claudeCredentials "$(cat ~/.claude/.credentials.json)"pulumi config set --secret claudeJson "$(cat ~/.claude.json)" -
Deploy everything with
pulumi up. This provisions the cluster, the gVisor pool, the Agent Sandbox controller, the Tailscale operator, and one sandbox per developer, in a single gesture:Terminal window pulumi up -
Once it’s up, the tailnet URL for each sandbox is a stack output. Open it in a browser (as the Tailscale-authenticated owner) to land in a VS Code IDE with Claude Code installed:
Terminal window pulumi stack output sandboxUrlsTo talk to the cluster with
kubectl:Terminal window pulumi stack output kubeConfig --show-secrets > kc.yamlKUBECONFIG=kc.yaml kubectl get sandboxes
Configuration#
| Key | Description | Default |
|---|---|---|
gcp:project | GCP project to deploy into | (required) |
gcp:zone | GCP zone | us-central1-a |
developers | JSON array of { name, email }; one sandbox each | (required) |
nodeMachineType | Machine type for the gVisor sandbox pool | e2-standard-4 |
sandboxNodeCount | Nodes in the gVisor sandbox pool | 1 |
systemNodeCount | Nodes in the default (controller/system) pool | 1 |
agentSandboxVersion | kubernetes-sigs/agent-sandbox release tag to install | v0.5.2 |
masterVersion | GKE control-plane version | latest available |
tailscaleOauthClientId/Secret | Tailscale operator OAuth client (secret) | (required) |
tailscaleAclClientId/Secret | Tailscale ACL-editing OAuth client (secret) | (required) |
claudeCredentials | Claude Code OAuth blob mounted into each sandbox (secret) | (required) |
claudeJson | Claude Code .claude.json onboarding stub (secret) | (required) |
Cleaning Up#
pulumi destroypulumi stack rm devpulumi destroy also restores Tailscale’s default ACL, so the tailnet isn’t left governed by the rules
of a demo that no longer exists.
Learn More#
For the reasoning behind each piece of this program — why a container alone isn’t a security boundary,
what the Sandbox CRD actually gives you, and how the gVisor node pool and the tailnet ACL fit together —
read Kubernetes Agent Sandbox: what it is and how to deploy it with Pulumi.