Run OpenClaw in a Daytona Sandbox via SDK
Learn how to run OpenClaw inside a Daytona sandbox using the Daytona SDK — create a sandbox programmatically and access the dashboard via a preview URL.
This guide shows how to run OpenClaw inside a Daytona sandbox using the Daytona SDK. The script automatically creates and configures a sandbox with OpenClaw and provides an authenticated preview URL for using OpenClaw in the browser.
1. Workflow Overview
When you run the script, it creates a Daytona sandbox, starts the OpenClaw gateway inside it, and prints a preview link for the dashboard:
$ npm start
Creating Daytona sandbox...
Configuring OpenClaw...
Starting OpenClaw...
(Ctrl+C to stop the script; the sandbox keeps running)
🔗 Secret link to Control UI: https://18790-xxxx.proxy.daytona.works#token=...
The sandbox is private - open the link while signed in to Daytona.
OpenClaw is ready.Open the provided link in your browser to connect to the OpenClaw Control UI. The sandbox is private, so open it while signed in to Daytona; the preview URL returns 401 otherwise. The link carries the gateway token as a URL fragment (the Control UI consumes it and strips it from the address bar), and your browser pairs silently on the first attempt — no device-approval prompt.
Two layers therefore guard the dashboard: Daytona authentication on the preview URL, and the gateway token in the link. Still treat the link as a secret.
You can use the Control UI to chat with your assistant, configure Telegram and WhatsApp, and manage sessions. When you exit the script (Ctrl+C), the sandbox will not be deleted unless sandbox persistence is disabled.
2. Project Setup
Clone the Repository
Clone the Daytona repository and go to the example directory:
git clone https://github.com/daytona/guides.git
cd guides/typescript/openclawConfigure Environment
Get your API key from the Daytona Dashboard.
Copy .env.example to .env and add your Daytona API key:
DAYTONA_API_KEY=your_daytona_keyA default OpenClaw configuration is stored in openclaw.json. You can customize it according to the configuration reference. You can also add additional environment variables to .env.sandbox (e.g. ANTHROPIC_API_KEY for Claude) and they will be loaded into the sandbox.
Alternative: Inject the Key as a Daytona Secret
The default setup loads everything in .env.sandbox - including ANTHROPIC_API_KEY - into the sandbox as plain environment variables, so anything running inside the sandbox (OpenClaw, its agents, any code they run) can read the raw key with env. Daytona Secrets keep the raw value out of the sandbox entirely: the environment variable holds only an opaque placeholder (dtn_secret_<id>), and Daytona's outbound proxy substitutes the real value into HTTPS request headers at egress - and only for requests to the hosts the Secret allows. An agent that dumps the environment or exfiltrates it never sees a usable key.
The Secret-based flow needs @daytona/sdk 0.192.0 or newer and a one-time Secret setup:
-
Create the Secret once for your organization - in the Daytona Dashboard or with a one-off script (save as
create-secret.tsin the project directory and runnpx tsx create-secret.ts):import { Daytona } from '@daytona/sdk' import * as dotenv from 'dotenv' import { readFileSync } from 'node:fs' dotenv.config() // DAYTONA_API_KEY from .env async function main() { const sandboxEnv = dotenv.parse(readFileSync('.env.sandbox', 'utf8')) if (!sandboxEnv.ANTHROPIC_API_KEY) throw new Error('ANTHROPIC_API_KEY is not set in .env.sandbox') const daytona = new Daytona() await daytona.secret.create({ name: 'anthropic-api-key', value: sandboxEnv.ANTHROPIC_API_KEY, hosts: ['api.anthropic.com'], // the only host the real key may be sent to }) } main() -
In
src/index.ts, add asecrets:mapping (environment variable name to Secret name) to the sandbox creation, and delete theANTHROPIC_API_KEYline from.env.sandboxso the raw key is no longer injected. Any other variables in.env.sandboxkeep flowing into the sandbox throughenvVarsas before:const sandbox = await daytona.create({ snapshot: DAYTONA_SNAPSHOT, autoStopInterval: 0, envVars: readEnvFile(ENV_SANDBOX_PATH), + secrets: { + ANTHROPIC_API_KEY: 'anthropic-api-key', + }, public: MAKE_PUBLIC, })
Inside the sandbox, env now shows ANTHROPIC_API_KEY=dtn_secret_..., yet OpenClaw still authenticates: the key is sent as the x-api-key HTTPS request header to api.anthropic.com, where the proxy swaps in the real value. Substitution happens only in HTTPS request headers toward allowed hosts - requests to any other host carry the harmless placeholder. If you configure additional providers in .env.sandbox, create one Secret per key with that provider's API host. See the Secrets documentation for the full substitution scope.
Run the Example
Install dependencies and run:
npm install
npm startThe script creates the sandbox, starts the OpenClaw gateway, and prints a secret link with the token in the URL.
3. How It Works
- The script creates a Daytona sandbox with
DAYTONA_SNAPSHOT(e.g.daytona-medium) and loads env vars from.env.sandbox. The sandbox is private unless you setMAKE_PUBLIC. - A preview link for
LOCAL_PROXY_PORTis resolved first, because its origin has to be allowlisted in the gateway config before the gateway starts. - Your local
openclaw.jsonis merged with built-in config — the generated gateway auth token and that preview origin asgateway.controlUi.allowedOrigins— and written to~/.openclaw/openclaw.jsonin the sandbox. The gateway binds loopback only, so nothing in the sandbox is reachable from outside except through the proxy below. - The OpenClaw gateway is started on
OPENCLAW_PORTvia process execution, and the script waits until it responds (retrying the start if needed). src/local-pairing-proxy.cjsis uploaded and started onLOCAL_PROXY_PORT. OpenClaw pairs every new browser as a device before it can use the Control UI, but it silently auto-approves pairing for clean local (loopback) connections once token auth succeeds. Daytona's preview proxy adds forwarding headers that make browsers look remote, so this proxy strips them before forwarding to the gateway — your browser is treated as a local client and pairs instantly, with no approval prompt.- The preview link is printed with the gateway token appended as a
#token=...URL fragment. - On Ctrl+C, the sandbox is deleted unless
PERSIST_SANDBOXistrue(the default), in which case it keeps running.
4. Key Constants
You can change behavior by editing the constants in src/index.ts:
| Constant | Default | Description |
|---|---|---|
PERSIST_SANDBOX | true | When true, the sandbox is not deleted when the script exits |
MAKE_PUBLIC | false | When false the sandbox stays private and the preview URL requires Daytona authentication |
OPENCLAW_PORT | 18789 | OpenClaw Gateway and Control UI port |
SHOW_LOGS | true | Stream OpenClaw stdout/stderr to the terminal |
LOCAL_PROXY_PORT | 18790 | In-sandbox pairing proxy port; the preview link targets it |
DAYTONA_SNAPSHOT | daytona-medium | Sandbox snapshot with OpenClaw preinstalled |
Key advantages:
- Secure, isolated execution in a Daytona sandbox
- Two-layer access: Daytona authentication on the private preview URL plus the gateway token
- No pairing prompts — browsers arriving via the preview link pair silently as local clients (token auth still required)
- Control UI and channels accessible via the secret preview link
- Optional: keep the sandbox running after exit (
PERSIST_SANDBOX)