Docker Sandboxes
Docker Sandboxes (the sbx CLI) runs agents in local microVMs. Each sandbox gets its own Docker daemon, filesystem, and network, and all egress is forced through a host-side gateway with a deny-by-default domain allowlist (sbx policy). That gateway is the seam varlock plugs into: point the agent at a varlock broker, allow the broker in policy, and the agent holds only placeholders while varlock injects real secrets at the wire.
The recommended shape for local development is a broker on your own machine: secrets, resolver plugins, biometric unlock, and the interactive request log all stay on the host, and the sandbox reaches the broker through the sbx gateway. To share one broker across a fleet, run it on infrastructure you operate and reach it at a public URL instead.
How sbx egress works
Section titled “How sbx egress works”Three facts about the gateway shape the setup:
- Everything goes through the gateway. Inside a sandbox,
HTTP_PROXY/HTTPS_PROXYpoint atgateway.docker.internal:3128and the sbx CA is in the trust store. Raw TCP, UDP, and ICMP are blocked at the network layer, DNS included, so names are resolved by the gateway and a request to a host with no policy rule comes back as a403from the gateway rather than a name-resolution failure.varlock proxy run --urlhonors those proxy env vars and dials its tunnel through the gateway automatically, so no shim is needed. - The gateway can reach a host-local service when you allow it in policy. This is what makes a host broker reachable from the microVM.
- Traffic you route through a broker leaves from the broker’s machine, not the sandbox. Once the agent’s requests ride the varlock tunnel, sbx policy sees only the connection to the broker. Egress control moves to varlock, which is why the schema below sets
egress="strict".
Broker on your machine
Section titled “Broker on your machine”1. Schema
Section titled “1. Schema”Mark the secrets your agent uses with @proxy(domain=...), give each an explicit @placeholder, and set @proxyConfig={egress="strict"} in the header:
# @proxyConfig={egress="strict"}# ---# @proxy(domain="api.anthropic.com")# @placeholder=sk-ant-api03-000000000000000000000000ANTHROPIC_API_KEY=yourPreferredPlugin()Values are resolved by the broker on your host, so the right-hand side is whatever your setup already uses: a plugin call as shown, or a value the broker picks up from its own environment or a local .env file. An item left empty with no source fails validation and the broker will not start.
2. Start the broker on the host
Section titled “2. Start the broker on the host”--expose binds off-loopback and serves the tunnel, minting a data-plane token. Pin the token so you can hand the same value to the agent:
export VARLOCK_PROXY_TOKEN=$(uuidgen)varlock proxy start --expose --port 8080Bare --expose binds 0.0.0.0, so the broker is reachable from your whole network, not just the sandbox. The data-plane token gates it, but on an untrusted network (a cafe, a conference) firewall the port or stay off that network while the broker runs. Serving the tunnel requires an off-loopback bind, so a loopback-only broker is not an option even though the gateway would reach it.
3. Allow the broker in sbx policy
Section titled “3. Allow the broker in sbx policy”The gateway rewrites host.docker.internal to localhost before forwarding, and policy is matched against the destination it forwards to. So the rule that matches is for localhost, even though the agent connects to host.docker.internal:
sbx policy allow network "localhost:8080"4. Run the agent through the broker
Section titled “4. Run the agent through the broker”Install varlock in the sandbox, then wrap the agent command with proxy run --url. The agent connects to the broker at host.docker.internal, and varlock self-wires its placeholder env and CA certs over the tunnel:
# in a shell sandbox (sbx create --name my-sandbox shell . && sbx exec my-sandbox bash -lc '...'):npm i -g varlock
VARLOCK_PROXY_TOKEN=$YOUR_TOKEN \ varlock proxy run --url ws://host.docker.internal:8080 -- your-agent-commandThat is the whole path: the agent holds placeholders, the broker on your host injects real values only on verified TLS connections to hosts your schema allows, and every request is checked against your @proxy rules and recorded in the audit log.
Remote broker
Section titled “Remote broker”To share one broker across machines or a fleet, run it on infrastructure you operate and expose it at a URL that carries WebSockets. Because sbx allows direct TLS to allowlisted domains, the agent reaches a public wss:// broker directly (no gateway asymmetry), so you only allow the broker’s domain:
sbx policy allow network "broker.example.com"# in the sandbox:VARLOCK_PROXY_TOKEN=$YOUR_TOKEN \ varlock proxy run --url wss://broker.example.com -- your-agent-commandThe data-plane token gates the tunnel, and the gateway passes allowlisted TLS through without terminating it, so the tunnel’s TLS runs end to end and an intermediary sees only ciphertext. One caveat: sbx does terminate TLS for hosts it injects its own credentials into, and its CA is already in the sandbox trust store, so if your broker’s domain ever became such a host the tunnel would still connect while no longer being end to end. Keep the broker on a domain sbx has no reason to intercept. See the topologies overview and the E2B / Fly.io guides for the same broker shape on cloud providers.
varlock proxy vs sbx secrets
Section titled “varlock proxy vs sbx secrets”Docker Sandboxes ships its own credential injection (sbx secret): the gateway substitutes a stored value into request headers for a matching host. It covers the basic case. Route through a varlock broker when you want:
- Custody in your secret manager. Secrets come from wherever you already keep them through plugins (1Password, Vault, AWS, Doppler, …) and stay in your custody, instead of being copied into another store.
- One schema. Your
.env.schemadescribes every value, its type, and its routing in one declarative layer, legible to people and agents alike. - Response scrubbing. varlock scans response bodies and redacts injected secret values, so an allowlisted endpoint that echoes a request header cannot hand the real secret back to the agent.
- Policy and audit. Match on path and method, hot-reload the schema, and keep your own audit log.
The two compose: sbx provides microVM isolation, and the varlock broker provides custody, injection, scrubbing, and (in strict mode) the egress boundary.
They also overlap on variable names: a sandbox starts with the env vars for sbx’s built-in services (ANTHROPIC_API_KEY, OPENAI_API_KEY, …) preset to the sentinel proxy-managed, whether or not you have stored a secret for them. varlock’s child env is layered on top, so a key your schema routes through @proxy reaches the agent as varlock’s placeholder and the varlock rule applies; keys your schema does not describe keep the sentinel and stay on the sbx path.
Trust model
Section titled “Trust model”The broker holds real secrets on whatever machine runs it. For a host broker in local development, that is your own machine, the same place the secrets already live. The agent’s microVM never holds them: a compromised or prompt-injected agent yields placeholders and only the requests your rules and egress mode allow.
Note where each boundary applies. sbx policy governs what the sandbox reaches directly, so keep it tight: the broker, plus anything the agent legitimately fetches on its own (package registries, and varlock.dev if you install by script). Everything the agent sends through the broker is governed by varlock instead, which is what egress="strict" is for. Adding a host to sbx policy does not widen or narrow the proxied path, and leaving varlock permissive does not get caught by sbx policy.
Treat the data-plane token like any shared secret (rotate by restarting the broker with a new one), and keep the broker’s port off untrusted networks, since --expose binds all interfaces.