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](installsdaytona). - 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​
- Sign in at app.daytona.io.
- Create an API key and export it:
export DAYTONA_API_KEY="dtn_..."
- Optionally point at another deployment or region:
export DAYTONA_API_URL="https://app.daytona.io/api" # the defaultexport 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
reprcarried back on a marked line of stdout that never reaches your logs. That is what makesresult.textwork above. - Rich display data — a figure, an HTML repr, a PNG — has no channel at all and
is not returned.
result.resultsonly ever holds the text value. - There is no interrupt:
sandbox.interrupt()answersFalse, 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:
| Source | Installed with |
|---|---|
A package list, a requirements.txt, a pyproject.toml and its uv.lock | uv pip sync --require-hashes: the snapshot holds exactly the lock |
A conda environment.yml | micromamba install of the explicit lock into the base's own interpreter, then the pip layer the solve recorded |
| A Dockerfile | Daytona'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.