Sign Up

Process Execution

Run shell commands and manage long-running processes in your sandboxes.

Daytona runs shell commands and long-running processes in sandboxes. Commands run inside the sandbox, so untrusted or generated commands execute in isolation from your application.

Process execution is available through the process module of a sandbox. Every command runs as a process: you can run a command and wait for its result, or start it in the background and control it through a handle. Sessions are shell processes that keep state, such as the working directory and environment variables, across the commands that run in them. To run Python, TypeScript, and JavaScript code, see code execution. For interactive terminals, see pseudo terminal (PTY). For real-time logs from long-running processes, see log streaming.

Use cases

Process execution is used when the work in a sandbox is a shell command or a program instead of a code snippet.

  • Build and test. Package installs, compilers, and test runners run in the sandbox, and their exit code and output return to the application. When a run times out, the result has no exit code and the command keeps running. The result holds stdout, stderr, and the output of both channels in recorded order.

  • Agent tools. An agent runs shell commands as tools and reads their results. The commands run inside the sandbox, isolated from the application that hosts the agent. Python, TypeScript, and JavaScript snippets run through code execution instead.

  • Servers and workers. A web server or worker starts in the background, and the application checks its state, reads its logs, or stops it through its handle. By default, an unnamed process keeps its record and logs for 10 minutes after it exits, and a named process keeps them until it is deleted. A retention option overrides either default.

  • Multi-step shell work. A session keeps the working directory and environment variables, so a sequence of commands runs as it does in one terminal. Commands in a session run one at a time and in order. A session survives pause and resume, but not stop and start.

  • Reconnects. An application reconnects to a process it started earlier and continues to read its output. Any client with the sandbox ID and the process ID can wait for the process, read its logs, or stop it. A process name resolves only while the process runs, and the ID still works after it exits.

Command execution

Execute commands

Run a command and wait for it to exit. The result holds the exit code and output.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

response = sandbox.process.run("ls -la")
print(response.exit_code, response.stdout)
ParameterTypeDescription
timeoutfloatSets how many seconds run() waits. When the timeout elapses, reason is "timed_out", exit_code is None, and the command keeps running. Without it, run() waits until the command exits.

The response has the following fields:

FieldDescription
stdoutHolds the output of the stdout channel.
stderrHolds the output of the stderr channel.
outputHolds the output of both channels in the order it was recorded. Writes to stdout and stderr that happen close together can appear out of order.

Set the user and shell

Run a command as a specific user or with a specific shell. The user must exist in the image and the shell must be installed.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

response = sandbox.process.run("whoami", user="nobody", shell="bash")
print(response.output)
ParameterTypeDescription
userstrSets the user that runs the command. The user must exist in the image.
shellstrSets the shell that runs the command. The shell must be installed in the image. The default is bash, or sh when bash is not installed.

Update the environment

Set or remove environment variables for every process started in the sandbox after the update, including PTYs, sessions, and the git commands that git operations run. Running processes and sessions keep their environment. Removing a variable affects only variables set through an earlier update; variables defined by the image stay.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Processes started after this call inherit the change
sandbox.update_env({"LOG_LEVEL": "debug"}, unset=["HTTP_PROXY"])

response = sandbox.process.run("echo $LOG_LEVEL")
print(response.stdout)
ParameterTypeDescription
envMapping[str, str]Adds or replaces variables. Each KEY=VALUE entry must be smaller than 128 KiB. The sandbox's added environment, which holds the variables set at creation, by secrets, by the platform, and by all updates, is at most 1 MiB in total, counting one extra byte per variable. An update that would grow it past 1 MiB is rejected; unset variables you no longer need to make room. On Windows, the existing environment plus the update is also capped at 32,767 UTF-16 characters. On macOS, it is capped at 512 KiB, half of ARG_MAX, and each variable counts 9 bytes in addition to its KEY=VALUE text.
unsetSequence[str]Removes variables. This parameter is keyword only.

Execute interactive commands

Run a command that reads input. Start the command with stdin open, write input to it, and close stdin when the command has all its input.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("tr a-z A-Z", stdin="open")

# Send input to the command, then end it
handle.write_stdin("hello\n")
handle.close_stdin()

wait = handle.wait()
page = handle.logs()
for frame in page.items:
    print(f"[{frame.channel}]: {frame.data}")
print(wait.exit_code)
ParameterTypeDescription
stdinstrSets the stdin of the command to "open" or "closed". The default is "closed".

Process control

Start a background process

Start a process without waiting for it to exit. The SDK returns a handle that you can use to wait for, read logs from, and stop the process.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("python -m http.server 8000", tag="web")
print(handle.id)
ParameterTypeDescription
tagstrGroups the process with others for listing and stopping processes by tag. The tag is 1 to 128 characters from A-Z, a-z, 0-9, ., _, and -, and is not . or ...

List processes

List the processes in a sandbox. Filter the list by state, kind, session, name, PID, or tag.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

running = sandbox.process.list(state="running")
for info in running.items:
    print(info.id, info.name, info.state, info.pid)

# Only the processes started with the tag
web = sandbox.process.list(tag="web")

Process states

StateMeaning
startingThe record exists; the process has not been spawned yet.
provisioningCode execution only: the language kernel is being installed or started. Progress is reported on the setup log channel.
runningThe process has a PID and is executing.
terminalThe process has finished. reason, exitCode, and signal describe how.
ReasonMeaning
exitedExited on its own; exitCode is set. A code execution that raised an exception exits with 1.
signaledKilled by a signal; signal is set. wait reports bare names (TERM, KILL), the API reports SIGTERM, SIGKILL.
timed_outThe timeoutMs set at start elapsed; the process was killed with SIGKILL. Also returned by wait itself when its own long-poll window elapses first, with no exitCode.
lostThe process disappeared without the daemon observing its exit, for example after a sandbox stop and start, or when its session was deleted while it was still running.
failedThe process could not be started or ran into a daemon-side error.

Log retention

Choose how long a process record and its logs are kept after the process exits. The policy is set when the process starts. Once a record is removed, the process can no longer be waited on or read. See log streaming for size caps and eviction.

PolicyBehavior
until_cleanupKept until the process is deleted (DELETE /processes/{id}). Default for API requests that set name; the SDK pty namespace also uses it.
on_exit_ttlKept for 10 minutes after exit, then removed. Default for API requests without name and for the SDK start and run methods; run does not accept none.
noneLogs are discarded as soon as the process exits and can no longer be read. The record is kept for 10 minutes, so wait still returns the exit result.
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Keep the record and logs until delete() is called
handle = sandbox.process.start("pip list", keep_logs="until_cleanup")
handle.wait()

for frame in handle.logs().items:
    print(frame.data, end="")
handle.delete()
ParameterTypeDescription
keep_logsstrSets how long the record and logs are kept after the process exits: "until_cleanup", "on_exit_ttl", or "none". The default is "on_exit_ttl".

Wait for a process

Wait for a process to finish, or until a timeout elapses.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("pip install requests")

# Wait up to 2 minutes; without timeout, wait() blocks until the process is terminal
result = handle.wait(timeout=120)
print(result.reason, result.exit_code)

Stop a process

Stop a running process by sending it a signal. The SDKs send the terminate signal.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("python -m http.server 8000")

# Sends SIGTERM
handle.kill()
ParameterTypeDescription
signalSignalNameSets the signal to send: TERM, KILL, INT, HUP, QUIT, USR1, or USR2. The default is "TERM".
escalate_afterfloatSends escalate_to if the process is still running after this many seconds. The value is at most 600.
escalate_toSignalNameSets the signal sent after escalate_after. The default is "KILL".

Stop processes by tag

Stop every process started with a tag, and everything those processes started, including descendants that detached and the survivors of tagged processes that already exited. The call returns once none of them is running, with the IDs of the tagged processes it stopped. Processes whose records were deleted or expired are stopped too but not listed, so an empty list does not mean nothing was stopped. The call does not delete records or logs; they are kept for as long as the keepLogs setting of each process allows. The tag can be used again right after the call.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

sandbox.process.start("python -m http.server 8000", tag="web")
sandbox.process.start("python -m http.server 8001", tag="web")

stopped = sandbox.process.kill_all(tag="web", timeout=30)
print(stopped)
ParameterTypeDescription
tagstrSets the tag of the processes to stop. This parameter is required.
timeoutfloatSets how many seconds to wait for the processes to exit, more than 0 and at most 60. Fractions are allowed. A value outside the range raises BadRequestError before any request is sent. The default is 10.

Reconnect to a process

Reconnect to a process from another worker or process. Processes are addressed by ID, so any client with the sandbox ID and the process ID can wait for, read logs from, and stop the process. To reconnect to an interpreter context, see reconnect to a context.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Worker A: start the process and persist the handle
handle = sandbox.process.start("pip install requests")
saved = handle.to_json()  # {"sandboxId": "...", "processId": "...", "kind": "exec"}

# Worker B: reconnect and wait for the same process
handle = sandbox.process.connect(saved)
result = handle.wait()

Entrypoint process

Get the details and logs of the sandbox entrypoint. It runs as a system process, so you can read it like any other process but not signal, write to, or delete it.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Entrypoint process details
info = sandbox.process.get("entrypoint")
print(f"Command: {info.argv or info.shell_command}")

# Entrypoint logs
handle = sandbox.process.connect("entrypoint")
for frame in handle.logs().items:
    print(f"[{frame.channel}]: {frame.data}")

Session operations

A session is a long-lived shell that runs the commands you send to it. Commands in a session run one at a time, in order, and share the shell state, such as the working directory, exported variables, and functions. To run commands in parallel, use separate sessions or background processes.

A session is a long-running shell process, and each command in it is an ordinary process with its own logs, exit code, timeout, and signals. Interrupting a command stops only that command, and the session keeps running. Sessions survive pause and resume, but not stop and start. Interpreter contexts run a language kernel instead of a shell.

Create a session

Create a shell session. The working directory and environment variables of the session apply to all of its commands.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

session = sandbox.process.create_session(cwd="/tmp", env={"CI": "1"}, name="build-session")
print(session.id)
ParameterTypeDescription
cwdstrSets the working directory of the shell. The default is the image's WORKDIR. When the image declares none, the default is the home directory of the user set at sandbox creation, or the daemon working directory when no user is set or the home directory does not exist. On macOS, the default is the home directory of that user.
envMapping[str, str]Sets environment variables applied when the shell starts.
shellstrSets the path or name of the shell. On Linux and macOS, it must be a POSIX shell: bash, sh, dash, zsh, or ksh. The shell must be installed in the image.
loginboolStarts a login shell when set to True.
namestrSets a name that commands can use in place of the session ID while the session is running. Names are unique among running processes.
tagstrSets the tag of the session. Every command in the session takes it, for listing and stopping processes by tag.

Execute session commands

Run commands in a session. Shell state set by one command is available to the next. A command timeout starts when the command starts, after any commands queued ahead of it. When the timeout elapses, Daytona stops the command and the session keeps running.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

with sandbox.process.create_session() as session:
    # Shell state persists between commands
    session.run("cd /tmp && export STAGE=dev")
    result = session.run("pwd && echo $STAGE", execution_timeout=30)
    print(result.stdout)

    # Interrupt one command; the session keeps running
    command = session.start("sleep 600")
    command.kill(signal="INT")
ParameterTypeDescription
timeoutfloatSets how many seconds run() waits for the command.
execution_timeoutfloatSets how many seconds the command can run before Daytona stops it. The session keeps running.
namestrSets a name for the command process.

Reconnect to a session

Reconnect to a session from another worker or process. Sessions are addressed by ID, so any client with the sandbox ID and the session ID can run commands in the session and read its status. Reconnecting sends no request; the next call on the session does.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Worker A: create the session and persist it
session = sandbox.process.create_session()
session.run("cd /tmp")
saved = session.to_json()  # {"sandboxId": "...", "sessionId": "..."}

# Worker B: reconnect and run in the same shell
session = sandbox.process.connect_session(saved)
result = session.run("pwd")

Get session status

Get the status of a session, including the current working directory of its shell, and list the commands that ran in it.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()
session.run("cd /tmp")

info = session.get()
print(info.state, info.cwd)

listed = sandbox.process.list(session_id=session.id)
for proc in listed.items:
    print(f"Process: {proc.id}, Command: {proc.shell_command}, Exit Code: {proc.exit_code}")

List sessions

List the shell sessions in a sandbox.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()

sessions = sandbox.process.list(kind="session")
for proc in sessions.items:
    print(proc.id, proc.state)

Get session command

Get a command that ran in a session by its process ID.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()
session.run("echo hello")

listed = sandbox.process.list(session_id=session.id)
command = sandbox.process.get(listed.items[0].id)
print(f"Command: {command.shell_command}, Exit Code: {command.exit_code}")

Delete a session

Delete a session. This ends its shell, stops its running commands, and removes the records of its commands.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()
session = sandbox.process.create_session()

session.delete()

Resource management

A process kept with until_cleanup keeps its record and logs until you delete it. The API applies this policy to processes started with a name; the SDKs apply it only when you pass it. Delete such a process when you no longer need it; deleting stops the process if it is still running.

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

handle = sandbox.process.start("sleep 3600", name="my-process", keep_logs="until_cleanup")
try:
    pass  # Do work...
finally:
    handle.delete()  # kills the process if it is still running

Permissions

Process execution runs through the toolbox of a sandbox. Access is governed by one project permission, which you can grant to an organization role or an API key.

A sandbox access token with the toolbox:exec operation grants the same access to a single sandbox, without an API key. The sandbox read permission alone does not grant toolbox access.

PermissionGrants
write:sandboxesRun commands, processes, and sessions, and update the environment