Skip to main content

Run agents in a cloud sandbox (Daytona)

Daytona runs each agent in an isolated cloud sandbox instead of the runner container. This page sets it up on an existing deployment. For what the sandbox isolates, see Sandbox isolation and security.

Every variable on this page is set on the runner service. Defaults and validation rules are in the runner reference.

Before you start

  • A Daytona account and an API key.
  • A running deployment. See the Quick start.

1. Enable the provider

To serve both local and Daytona runs from one runner:

AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS=local,daytona
AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER=daytona

To run only Daytona, so no harness ever runs inside the runner container:

AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS=daytona
AGENTA_RUNNER_DEFAULT_SANDBOX_PROVIDER=daytona

The default must be one of the enabled providers, or the runner fails to start.

2. Set the Daytona credentials

AGENTA_RUNNER_DAYTONA_API_KEY=<daytona-api-key>
AGENTA_RUNNER_DAYTONA_API_URL=https://app.daytona.io/api
AGENTA_RUNNER_DAYTONA_TARGET=eu

The API key is required whenever daytona is enabled. The URL and the target are optional.

For Helm:

agentRunner:
providers:
enabled: [local, daytona]
default: daytona
daytona:
apiKeySecretRef:
name: agenta-runner-daytona
key: api-key
apiUrl: https://app.daytona.io/api
target: eu

3. Build the sandbox snapshot

A snapshot is a prebuilt sandbox image stored in your Daytona account. Every sandbox the runner creates starts from one, so whatever the snapshot contains is what your agents can run.

Agenta ships the build recipe rather than a prebuilt snapshot, so you build it in your own Daytona account:

cd services/runner/images/sandbox/daytona
DAYTONA_API_KEY=<daytona-api-key> DAYTONA_TARGET=eu uv run build_snapshot.py --force

The recipe starts from the sandbox base image, which already contains Claude Code, Codex, and OpenCode. It adds the Pi harness at the version the runner expects, plus the FUSE tooling that mounts durable working directories inside the sandbox. It builds a snapshot named agenta-agent-sandbox-v1 and sets each sandbox to 2 vCPU, 4 GB of memory, and 5 GB of disk.

Point the runner at it:

AGENTA_RUNNER_DAYTONA_SNAPSHOT=agenta-agent-sandbox-v1

For Helm:

agentRunner:
providers:
daytona:
snapshot: agenta-agent-sandbox-v1

To give agents extra tools (a browser, the gh CLI, a language runtime) or different sandbox resources, add them to the recipe. See Customize the agent runtime.

info

AGENTA_RUNNER_DAYTONA_SNAPSHOT and AGENTA_RUNNER_DAYTONA_IMAGE are mutually exclusive. Set one or the other. If you set neither, the runner starts sandboxes from agenta-agent-sandbox-v1.

4. Make the store reachable from the sandbox

Agents keep files across turns in a working directory that lives in the durable store. A Daytona sandbox mounts that directory from inside the cloud, so it reaches the store over the public internet. If the store endpoint is not publicly reachable, the sandbox starts without the mount, working directories are ephemeral, and the runner logs a mount-degradation warning.

This applies to you when AGENTA_STORE_ENDPOINT_URL points at a host the internet cannot resolve: a Compose service name such as http://seaweedfs:8333 (the default for the bundled store), localhost, or a private IP. If it already points at a public S3 endpoint, as on Railway and on Kubernetes with public ingress, skip this step.

There are three ways to fix it. Most self-hosted deployments use the first.

Expose the bundled store through traefik. The gh Compose stacks can publish the bundled store on its own subdomain. This is off by default. Pick a hostname such as store.example.com and add a DNS record that points it at the same server as your main domain. Then set these variables in your env file (.env.oss.gh, or .env.oss.gh.ssl on the --ssl stack):

AGENTA_STORE_TRAEFIK_ENABLE=true
AGENTA_STORE_DOMAIN=store.example.com
AGENTA_STORE_ENDPOINT_URL=https://store.example.com

Recreate the stack. On the --ssl stack, traefik requests a Let's Encrypt certificate for the new hostname from the same resolver it uses for your main domain. On the plain gh stack, traefik serves the store on port 80 and your own proxy terminates TLS. The router matches on the hostname and never rewrites the path, which the S3 request signature requires, so do not place the store behind a path prefix.

To verify, run an agent on Daytona (step 5) and write a file. The runner logs remote mounted ... (verified alive) for the session instead of a mount degraded line, and the file is still there on the next turn.

Point the store at an external S3 endpoint. Use an external S3-compatible bucket (AWS S3, Cloudflare R2, MinIO) that is already reachable from the internet, and set the store endpoint to its URL:

AGENTA_STORE_ENDPOINT_URL=https://<your-bucket-endpoint>

Tunnel the bundled store (dev only). The dev Compose stack ships an ngrok service that publishes the bundled store on a public URL, which the runner discovers automatically. Create an ngrok authtoken, put it in your env file, and start the stack with the with-tunnel profile:

# in hosting/docker-compose/oss/.env.oss.dev
NGROK_AUTHTOKEN=<your-ngrok-authtoken>
bash ./hosting/docker-compose/run.sh --oss --dev

run.sh enables the with-tunnel profile by default (--no-tunnel turns it off). If you drive Compose directly, pass --profile with-tunnel. Confirm the tunnel is up:

docker compose logs ngrok | grep "started tunnel"

5. Smoke test

Recreate the runner, then run an agent with the Daytona sandbox selected. The runner logs its resolved configuration on startup:

runner providers enabled=[local,daytona] default=daytona
runner daytona target=eu artifact=snapshot:agenta-agent-sandbox-v1

The first run creates a sandbox in your Daytona account. Later turns reuse a warm sandbox until it stops or is deleted. See warm sessions and scale.

Personal subscriptions are local-only

A personal Pi, Claude, or ChatGPT login authenticates a harness through a directory mounted into the runner container. Agenta does not upload that login to a Daytona sandbox, so Daytona runs use a model API key instead. See Use your own subscription.

Troubleshooting

A run fails with Sandbox provider 'daytona' is not enabled on this deployment

Cause: daytona is missing from AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS.

Solution: Add it (step 1) and recreate the runner.

The runner fails to start with AGENTA_RUNNER_DAYTONA_API_KEY is required

Cause: daytona is enabled without a key.

Solution: Set AGENTA_RUNNER_DAYTONA_API_KEY (step 2).

The runner fails to start with AGENTA_RUNNER_DAYTONA_SNAPSHOT and AGENTA_RUNNER_DAYTONA_IMAGE are mutually exclusive

Cause: Both are set.

Solution: Set only one of them.

The runner fails to start with a Daytona authentication error

Cause: The API key is wrong, or it has no access to the target.

Solution: Check AGENTA_RUNNER_DAYTONA_API_KEY and AGENTA_RUNNER_DAYTONA_TARGET against your Daytona account.

Working directories do not persist across turns

Cause: The sandbox cannot reach the store, so it mounted nothing. The runner logs a mount-degradation warning.

Solution: Make the store publicly reachable (step 4).

Next