ExDaytona.Sandbox (ex_daytona v0.4.0)

Copy Markdown View Source

High-level sandbox facade: lifecycle, command execution, and files.

Wraps the generated ExDaytona.Api.Sandbox (platform) and toolbox APIs (Api.Process, Api.FileSystem) with idiomatic Elixir — snake_case options, {:error, %ExDaytona.Error{}} failures, and a sandbox struct that carries its client so toolbox calls need no extra wiring:

{:ok, client} = ExDaytona.Client.new()
{:ok, sandbox} = ExDaytona.Sandbox.create(client, snapshot: "...")

{:ok, %{exit_code: 0, output: out}} = ExDaytona.Sandbox.exec(sandbox, "echo hello")

:ok = ExDaytona.Sandbox.write_file(sandbox, "/workspace/hello.txt", "hi")
{:ok, "hi"} = ExDaytona.Sandbox.read_file(sandbox, "/workspace/hello.txt")

:ok = ExDaytona.Sandbox.delete(sandbox)

For sandbox operations the facade doesn't cover, drop down to the generated modules with ExDaytona.Client.conn/1 (platform) or toolbox_conn/1 (toolbox).

Summary

Types

t()

A sandbox bound to the client that created or fetched it.

Functions

Poll until the sandbox reaches state (a string or list of strings).

The sandbox's build logs so far, as a binary. Only sandboxes built from build info have build logs; snapshot-based sandboxes return an error.

Create a sandbox and (by default) wait for it to reach the started state.

Delete a sandbox (by struct or id). Returns :ok.

Run a shell command inside the sandbox.

Expire a signed preview URL before its natural expiry. Returns :ok.

Fetch a sandbox by id or name.

The sandbox id.

List sandboxes. Accepts the generated list_sandboxes filters (:limit, :name, :labels, :states, ...) and returns {:ok, %{items: [...], next_cursor: cursor}} where items are ExDaytona.Model.SandboxListItem structs.

List files at path inside the sandbox (default: the working directory). Delegates to ExDaytona.FS.list_files/2.

The preview URL for a port the sandbox is listening on. Returns {:ok, %{url, token}} — for private sandboxes, send the token as the x-daytona-preview-token header (browsers hitting the URL directly get Daytona's auth flow).

Read the file at path inside the sandbox. Delegates to ExDaytona.FS.read_file/2.

Re-fetch the sandbox's current state from the API.

Revoke the sandbox's SSH access. Returns :ok.

Run a code snippet with a fresh interpreter each time (stateless). Python, JavaScript, and TypeScript are supported; for persistent state across runs use ExDaytona.CodeInterpreter.

A signed (self-authenticating, expiring) preview URL for a port — shareable without exposing an auth token header. Returns {:ok, %{url, token}}; expire it early with expire_signed_preview_url/3.

Create SSH access to the sandbox. Returns {:ok, %{token, ssh_command, expires_at}} — run ssh_command in a terminal, or use token as the SSH username against Daytona's SSH gateway.

Start a stopped sandbox. Waits for started unless wait: false.

The sandbox state as a string ("started", "stopped", ...).

Stop a running sandbox. Waits for stopped unless wait: false.

Follow the sandbox's build logs in real time: fun is invoked with each chunk as it is produced, and the call returns :ok when the build finishes and the stream closes.

The Tesla client for this sandbox's toolbox API, for generated toolbox operations the facade doesn't cover (Api.Git, Api.Lsp, Api.ComputerUse, ...). Fails if the sandbox has no toolboxProxyUrl yet (still starting).

Update the sandbox's network policy at runtime. At least one option is required

Validate an SSH access token (for building SSH gateways/tooling). Returns {:ok, %{valid: boolean, sandbox_id: id | nil}}.

Write content to path inside the sandbox. Delegates to ExDaytona.FS.write_file/3 — see ExDaytona.FS for the full file-system surface.

Types

t()

@type t() :: %ExDaytona.Sandbox{
  client: ExDaytona.Client.t(),
  info: ExDaytona.Model.Sandbox.t()
}

A sandbox bound to the client that created or fetched it.

info is the raw ExDaytona.Model.Sandbox (camelCase fields, as sent by the API); id/1 and state/1 are the ergonomic accessors.

Functions

await_state(sandbox, state, opts \\ [])

@spec await_state(t(), String.t() | [String.t()], keyword()) ::
  {:ok, t()} | {:error, ExDaytona.Error.t()}

Poll until the sandbox reaches state (a string or list of strings).

Fails fast with {:error, %Error{}} if the sandbox enters an error state (["error", "build_failed", "destroyed", "destroying"]), and with a timeout error after :timeout milliseconds (default 120_000; poll interval :poll_interval, default 1_000).

build_logs(sandbox)

@spec build_logs(t()) :: {:ok, binary()} | {:error, ExDaytona.Error.t()}

The sandbox's build logs so far, as a binary. Only sandboxes built from build info have build logs; snapshot-based sandboxes return an error.

create(client, opts \\ [])

@spec create(
  ExDaytona.Client.t(),
  keyword()
) :: {:ok, t()} | {:error, ExDaytona.Error.t()}

Create a sandbox and (by default) wait for it to reach the started state.

Options

  • :wait — wait until the sandbox is running (default true)
  • :timeout — max milliseconds to wait (default 120_000)
  • :poll_interval — milliseconds between state polls (default 1_000)
  • :image — build the sandbox declaratively instead of from a snapshot: an ExDaytona.Image or a raw Dockerfile string. Building takes longer than starting from a snapshot — raise :timeout accordingly and watch progress with stream_build_logs/3.
  • sandbox settings, all optional: :snapshot, :name, :user, :cpu, :gpu, :gpu_type, :memory, :disk, :env, :labels, :public, :target, :volumes, :spot, :linked_sandbox, :outbound_proxy_url, :otel_endpoint_override, :ttl_minutes, :auto_stop_interval, :auto_pause_interval (at most one of stop/pause may be non-zero), :auto_archive_interval, :auto_delete_interval
  • network policy: :network_block_all, :network_allow_list (CIDRs), :domain_allow_list (comma-separated domains) — changeable later with update_network_settings/2
  • :secrets — vault-backed secret bindings, a list of single-entry maps %{"ENV_VAR" => "vault-secret-name"} (see ExDaytona.Secrets)

With no settings the API uses the organization's default snapshot.

env values are not secrets

Ordinary :env values are stored and transmitted in plain text — they are visible to sandbox processes, in sandbox metadata, and to the provider API. Put credentials in vault secrets and mount them with :secrets instead.

delete(sandbox_or_id, client \\ nil)

@spec delete(t() | String.t(), ExDaytona.Client.t() | nil) ::
  :ok | {:error, ExDaytona.Error.t()}

Delete a sandbox (by struct or id). Returns :ok.

exec(sandbox, command, opts \\ [])

@spec exec(t(), String.t(), keyword()) ::
  {:ok, %{exit_code: integer() | nil, output: String.t()}}
  | {:error, ExDaytona.Error.t()}

Run a shell command inside the sandbox.

Options

  • :cwd — working directory
  • :env — extra environment variables (map)
  • :timeout — command timeout in seconds (API-side)

Returns {:ok, %{exit_code: integer, output: binary}}.

expire_signed_preview_url(sandbox, port, token)

@spec expire_signed_preview_url(t(), pos_integer(), String.t()) ::
  :ok | {:error, ExDaytona.Error.t()}

Expire a signed preview URL before its natural expiry. Returns :ok.

get(client, id_or_name)

@spec get(ExDaytona.Client.t(), String.t()) ::
  {:ok, t()} | {:error, ExDaytona.Error.t()}

Fetch a sandbox by id or name.

id(sandbox)

@spec id(t()) :: String.t()

The sandbox id.

list(client, opts \\ [])

@spec list(
  ExDaytona.Client.t(),
  keyword()
) ::
  {:ok,
   %{
     items: [ExDaytona.Model.SandboxListItem.t()],
     next_cursor: String.t() | nil
   }}
  | {:error, ExDaytona.Error.t()}

List sandboxes. Accepts the generated list_sandboxes filters (:limit, :name, :labels, :states, ...) and returns {:ok, %{items: [...], next_cursor: cursor}} where items are ExDaytona.Model.SandboxListItem structs.

:labels may be a map — it is JSON-encoded into the server-side label filter, the primitive for attributing sandboxes to your application's own scopes (e.g. per end-user of a SaaS product):

Sandbox.list(client, labels: %{"my-app/tenant" => "user-123"})

A pre-encoded JSON string is also accepted.

list_files(sandbox, path \\ nil)

@spec list_files(t(), String.t() | nil) ::
  {:ok, [ExDaytona.Model.FileInfo.t()]} | {:error, ExDaytona.Error.t()}

List files at path inside the sandbox (default: the working directory). Delegates to ExDaytona.FS.list_files/2.

preview_url(sandbox, port)

@spec preview_url(t(), pos_integer()) ::
  {:ok, ExDaytona.Sandbox.PreviewUrl.t()} | {:error, ExDaytona.Error.t()}

The preview URL for a port the sandbox is listening on. Returns {:ok, %{url, token}} — for private sandboxes, send the token as the x-daytona-preview-token header (browsers hitting the URL directly get Daytona's auth flow).

read_file(sandbox, path)

@spec read_file(t(), String.t()) :: {:ok, binary()} | {:error, ExDaytona.Error.t()}

Read the file at path inside the sandbox. Delegates to ExDaytona.FS.read_file/2.

refresh(sandbox)

@spec refresh(t()) :: {:ok, t()} | {:error, ExDaytona.Error.t()}

Re-fetch the sandbox's current state from the API.

revoke_ssh_access(sandbox)

@spec revoke_ssh_access(t()) :: :ok | {:error, ExDaytona.Error.t()}

Revoke the sandbox's SSH access. Returns :ok.

run_code(sandbox, code, opts \\ [])

@spec run_code(t(), String.t(), keyword()) ::
  {:ok,
   %{
     exit_code: integer() | nil,
     result: String.t() | nil,
     artifacts: ExDaytona.Model.CodeRunArtifacts.t() | nil
   }}
  | {:error, ExDaytona.Error.t()}

Run a code snippet with a fresh interpreter each time (stateless). Python, JavaScript, and TypeScript are supported; for persistent state across runs use ExDaytona.CodeInterpreter.

Options

  • :language"python" | "javascript" | "typescript" (server default when omitted)

  • :argv — command-line arguments (list)
  • :env — environment variables (map)
  • :timeout — execution timeout in seconds (API-side)

Returns {:ok, %{exit_code, result, artifacts}} where result is the combined output and artifacts carries chart captures when present.

signed_preview_url(sandbox, port, opts \\ [])

@spec signed_preview_url(t(), pos_integer(), keyword()) ::
  {:ok, ExDaytona.Sandbox.PreviewUrl.t()} | {:error, ExDaytona.Error.t()}

A signed (self-authenticating, expiring) preview URL for a port — shareable without exposing an auth token header. Returns {:ok, %{url, token}}; expire it early with expire_signed_preview_url/3.

Options: :expires_in_seconds — link lifetime (server default when omitted).

ssh_access(sandbox, opts \\ [])

@spec ssh_access(
  t(),
  keyword()
) :: {:ok, ExDaytona.Sandbox.SshAccess.t()} | {:error, ExDaytona.Error.t()}

Create SSH access to the sandbox. Returns {:ok, %{token, ssh_command, expires_at}} — run ssh_command in a terminal, or use token as the SSH username against Daytona's SSH gateway.

Options: :expires_in_minutes — token lifetime (server default when omitted).

start(sandbox, opts \\ [])

@spec start(
  t(),
  keyword()
) :: {:ok, t()} | {:error, ExDaytona.Error.t()}

Start a stopped sandbox. Waits for started unless wait: false.

state(sandbox)

@spec state(t()) :: String.t() | nil

The sandbox state as a string ("started", "stopped", ...).

stop(sandbox, opts \\ [])

@spec stop(
  t(),
  keyword()
) :: {:ok, t()} | {:error, ExDaytona.Error.t()}

Stop a running sandbox. Waits for stopped unless wait: false.

stream_build_logs(sandbox, fun, opts \\ [])

@spec stream_build_logs(t(), (binary() -> any()), keyword()) ::
  :ok | {:error, ExDaytona.Error.t()}

Follow the sandbox's build logs in real time: fun is invoked with each chunk as it is produced, and the call returns :ok when the build finishes and the stream closes.

Options: :timeout — max milliseconds to wait between chunks (default :infinity).

toolbox_conn(sandbox)

@spec toolbox_conn(t()) :: {:ok, Tesla.Env.client()} | {:error, ExDaytona.Error.t()}

The Tesla client for this sandbox's toolbox API, for generated toolbox operations the facade doesn't cover (Api.Git, Api.Lsp, Api.ComputerUse, ...). Fails if the sandbox has no toolboxProxyUrl yet (still starting).

update_network_settings(sandbox, opts)

@spec update_network_settings(
  t(),
  keyword()
) :: {:ok, t()} | {:error, ExDaytona.Error.t()}

Update the sandbox's network policy at runtime. At least one option is required:

  • :domain_allow_list — comma-separated allowed domains
  • :network_allow_list — comma-separated allowed CIDRs
  • :network_block_all — block all network access

Returns the updated sandbox. Organizations on tiers with enforced network restrictions reject sandbox-level overrides with a 400 ("Network access is restricted…", verified live) — see Daytona's tier-based network restriction docs.

validate_ssh_access(client, token)

@spec validate_ssh_access(ExDaytona.Client.t(), String.t()) ::
  {:ok, %{valid: boolean() | nil, sandbox_id: String.t() | nil}}
  | {:error, ExDaytona.Error.t()}

Validate an SSH access token (for building SSH gateways/tooling). Returns {:ok, %{valid: boolean, sandbox_id: id | nil}}.

Authentication

This endpoint authenticates gateway infrastructure — with a regular user API key it returns 403 "Invalid authentication context".

write_file(sandbox, path, content)

@spec write_file(t(), String.t(), iodata()) :: :ok | {:error, ExDaytona.Error.t()}

Write content to path inside the sandbox. Delegates to ExDaytona.FS.write_file/3 — see ExDaytona.FS for the full file-system surface.