Skip to main content

Daytona

Runs code in a Daytona sandbox — an isolated cloud sandbox that starts in well under a second.

State persists between calls. The variant drives the sandbox's code interpreter (sandbox.code_interpreter), which holds one Python namespace per context, rather than process.code_run, which is a fresh process per snippet:

sandbox.run_code("x = 40")
sandbox.run_code("x + 2").text # "42"
  • Requirements: code-sandboxes[daytona] (installs daytona).
  • Parameters: api_key, api_url, target, jwt_token, organization_id, snapshot, image, python_version, gpu_count, spot, delete_on_stop.

How To Obtain Daytona Credentials​

  1. Sign in at app.daytona.io.
  2. Create an API key and export it:
    export DAYTONA_API_KEY="dtn_..."
  3. Optionally point at another deployment or region:
    export DAYTONA_API_URL="https://app.daytona.io/api" # the default
    export DAYTONA_TARGET="eu" # your default region otherwise

JWT authentication works too, and then the organization has to be named alongside it: DAYTONA_JWT_TOKEN with DAYTONA_ORGANIZATION_ID.

Every one of these can be passed to the sandbox instead of exported. What is left out is read from the environment by the Daytona SDK, so passing nothing is not the same as passing None:

Sandbox.create(variant="daytona", api_key="dtn_...", target="eu")

Usage​

from code_sandboxes import Sandbox

with Sandbox.create(variant="daytona") as sandbox:
result = sandbox.run_code("import numpy as np; np.arange(5).sum()")
print(result.text) # "10"

Contexts​

The default namespace is shared by every call. create_context() asks Daytona for one it keeps apart, so two pieces of work can run in the same sandbox without seeing each other's variables:

with Sandbox.create(variant="daytona") as sandbox:
other = sandbox.create_context()

sandbox.run_code("secret = 1")
sandbox.run_code("secret", context=other).code_error.name # "NameError"

GPUs​

Daytona's own flavours — H100, H200, RTX-PRO-6000, RTX-4090, RTX-5090. A name Daytona does not have is refused before any sandbox is created, naming the ones it does:

Sandbox.create(variant="daytona", gpu="H100")
Sandbox.create(variant="daytona", gpu="H100", gpu_count=2)

Name several, comma-separated, to say which you would prefer. Daytona takes the first of them it can find, so a workload that runs on either does not fail because the better card is busy:

Sandbox.create(variant="daytona", gpu="H100,H200,RTX-4090")

A GPU — like any request for cpu or memory — is a machine specification, and Daytona accepts one only when the sandbox is built from an image. So asking for resources builds from Image.debian_slim() instead of starting from the default snapshot, and takes longer to come up. Pass image= to choose that image yourself, or snapshot= to start from a snapshot of your organization when you need no specification.

Spot GPUs​

spot=True runs on preemptible capacity: far cheaper than on-demand, and outside your organization's GPU quota — available capacity is the only limit. The price is that Daytona can take the sandbox back at any moment, without warning, when on-demand capacity needs it.

Sandbox.create(variant="daytona", gpu="H100,H200", spot=True)

Spot is GPU-only and is built from an image rather than from a snapshot. Both are checked here: spot=True without a gpu=, or together with a snapshot=, is refused with the reason rather than coming back as an API error.

Every GPU sandbox is created as ephemeral — auto_delete_interval=0, so it is deleted rather than kept when it stops. That is Daytona's rule, not this package's: it refuses any other value with "GPU sandboxes must be ephemeral". It applies to on-demand GPUs as much as to preemptible ones, so a GPU sandbox cannot be created detached and picked up later the way a plain one can.

An ordered list of GPUs is worth more on spot than anywhere else — what is free changes minute to minute.

Surviving preemption​

There is no warning and no webhook. What there is, is a timestamp: a reclaimed sandbox is marked and stays retrievable for 24 hours. Ask it directly:

if sandbox.preempted_at():
print("reclaimed, start again elsewhere")

You rarely have to. A reclaimed sandbox fails the way a dropped connection does, so run_code asks on your behalf and says which it was:

result = sandbox.run_code("train()")
if not result.execution_ok:
print(result.execution_error)
# "The spot sandbox was reclaimed at 2026-08-21T10:00:00Z: preemptible
# capacity is taken back when on-demand capacity needs it. ..."

The question costs a round trip, so it is only asked of a sandbox that could have been reclaimed at all.

Falling back to on-demand​

When there is no spot capacity, creation fails immediately — there is no queue to wait in. Falling back is left to you, deliberately: on-demand costs more and counts against your quota, and that is not a decision to make quietly on your behalf.

try:
sandbox = Sandbox.create(variant="daytona", gpu="H100", spot=True)
except Exception:
sandbox = Sandbox.create(variant="daytona", gpu="H100")

Network policy​

The policy of the configuration becomes Daytona's own network settings:

# No outbound network at all.
Sandbox.create(variant="daytona", network_policy="none")

# Only these domains.
Sandbox.create(
variant="daytona",
network_policy="allowlist",
allowed_hosts=["pypi.org", "files.pythonhosted.org"],
)

Lifecycle​

Leaving the with block deletes the sandbox. Daytona sandboxes otherwise outlive the program that made them, and one nobody deletes goes on costing storage. Pass delete_on_stop=False to stop it instead, leaving it in the organization to be started again:

with Sandbox.create(variant="daytona", delete_on_stop=False) as sandbox:
sandbox.run_code("open('/tmp/work', 'w').write('kept')")

code-sandboxes list -v daytona shows what is there; see the management guide.

Names and tags​

A Daytona name is an address — daytona.get(name) — and has to be unique within an organization, while the names this package generates are meant to be readable and may repeat. So the name and any tags travel as labels, next to created-by=code-sandboxes, which is what tells the sandboxes this package made from the rest of the organization's:

Sandbox.create(variant="daytona", name="nightly-report", tags={"team": "ai"})

What Is Not Reported​

The interpreter answers with stdout, stderr, and the error when the code raised. There is no execute_result on the wire, so:

  • The value of a trailing expression is captured for you — it is evaluated, bound, and its repr carried back on a marked line of stdout that never reaches your logs. That is what makes result.text work above.
  • Rich display data — a figure, an HTML repr, a PNG — has no channel at all and is not returned. result.results only ever holds the text value.
  • There is no interrupt: sandbox.interrupt() answers False, and a runaway execution is stopped by its timeout.

Variables cross as JSON (get_variable, set_variable, and everything built on them such as sandbox.commands.run), so a value that cannot be encoded comes back as its repr rather than as the object itself, and one that cannot be sent is refused with a reason.

Binary files go straight to Daytona's filesystem API rather than through a program that decodes them:

sandbox.files.write_bytes("/tmp/data.parquet", payload)
sandbox.files.read_bytes("/tmp/data.parquet")

Environments​

An Environment built for Daytona is a snapshot — built from the approved Datalayer base, the resolved lock, your files and postInstall commands, and an explicit tini -- sleep infinity entrypoint (Daytona's own default, otherwise, is a bare sleep infinity, with no init to reap orphans). The base already carries uv, the doctor and the sandbox contract's user and locale, so none of that is reinstalled — only the lock is genuinely per-build. The snapshot's own id is the artifact; a build also names it, for operability, but a launch never uses the name.

How the lock is installed depends on where the environment came from:

SourceInstalled with
A package list, a requirements.txt, a pyproject.toml and its uv.lockuv pip sync --require-hashes: the snapshot holds exactly the lock
A conda environment.ymlmicromamba install of the explicit lock into the base's own interpreter, then the pip layer the solve recorded
A DockerfileDaytona's Image.from_dockerfile, its approved FROM pinned to the resolved digest, then uv pip install --require-hashes of the lock — install, not sync, so what the Dockerfile installed stays

Deleted by id, and never stranded. Retention deletes a snapshot nothing keeps any more, by its id — a name is reused once its snapshot is gone — and a snapshot already gone counts as deleted. A build that fails after Daytona built its snapshot, at the core tier or at the record, deletes that snapshot before it reports the failure, so nothing is left in your account that no version names.

make env-build-daytona, in the environments folder of the examples repository, builds a version for Daytona from the command line.

Bring your own account: a build runs with the environment owner's own Daytona organization, not this package's ambient credentials — either DAYTONA_API_KEY, or DAYTONA_JWT_TOKEN together with DAYTONA_ORGANIZATION_ID, the same two forms Sandbox.create itself accepts (above). The snapshot exists only in that organization.

Resources and region are part of the snapshot, the one thing Daytona's own artifact carries that the other three variants' don't: the size class (small, medium, large) sets the CPU, memory and disk a build asks for, and the target a build's snapshot lands in is the one a launch starts from — a version that changes size class or region rebuilds a second, distinct artifact rather than resizing the first.

A GPU is part of the snapshot too. A GPU class (gpu-small, gpu-large) on the CUDA base bakes the spec's accelerator into it: its type, one of Daytona's GPUs, and its count. CPU and memory come from resources.hints, or Daytona's own size for that GPU, and the disk holds the CUDA base. Every sandbox of a GPU snapshot is ephemeral, as Daytona requires: DaytonaSandbox reads the snapshot's gpu and asks for it. The build's smoke test adds check 11 to the core tier: the GPUs asked for are visible, CUDA is the base's, and the host's driver is new enough for it. GPU compute is billed by Daytona to the owner's organization, which needs GPU credits.

Of the four variants, Daytona is the one whose build-time doctor check and live launch agree in full: the doctor is not run at build time (a build-time RUN step's PID 1 is Daytona's own build agent, not the snapshot's — the init check would fail there for a reason that says nothing about the finished artifact), but a real launched sandbox passes every check of the core tier, identity included.