Sign Up

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/openclaw

Configure 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_key

A 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:

  1. Create the Secret once for your organization - in the Daytona Dashboard or with a one-off script (save as create-secret.ts in the project directory and run npx 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()
  2. In src/index.ts, add a secrets: mapping (environment variable name to Secret name) to the sandbox creation, and delete the ANTHROPIC_API_KEY line from .env.sandbox so the raw key is no longer injected. Any other variables in .env.sandbox keep flowing into the sandbox through envVars as 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 start

The script creates the sandbox, starts the OpenClaw gateway, and prints a secret link with the token in the URL.

3. How It Works

  1. 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 set MAKE_PUBLIC.
  2. A preview link for LOCAL_PROXY_PORT is resolved first, because its origin has to be allowlisted in the gateway config before the gateway starts.
  3. Your local openclaw.json is merged with built-in config — the generated gateway auth token and that preview origin as gateway.controlUi.allowedOrigins — and written to ~/.openclaw/openclaw.json in the sandbox. The gateway binds loopback only, so nothing in the sandbox is reachable from outside except through the proxy below.
  4. The OpenClaw gateway is started on OPENCLAW_PORT via process execution, and the script waits until it responds (retrying the start if needed).
  5. src/local-pairing-proxy.cjs is uploaded and started on LOCAL_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.
  6. The preview link is printed with the gateway token appended as a #token=... URL fragment.
  7. On Ctrl+C, the sandbox is deleted unless PERSIST_SANDBOX is true (the default), in which case it keeps running.

4. Key Constants

You can change behavior by editing the constants in src/index.ts:

ConstantDefaultDescription
PERSIST_SANDBOXtrueWhen true, the sandbox is not deleted when the script exits
MAKE_PUBLICfalseWhen false the sandbox stays private and the preview URL requires Daytona authentication
OPENCLAW_PORT18789OpenClaw Gateway and Control UI port
SHOW_LOGStrueStream OpenClaw stdout/stderr to the terminal
LOCAL_PROXY_PORT18790In-sandbox pairing proxy port; the preview link targets it
DAYTONA_SNAPSHOTdaytona-mediumSandbox 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)