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.
- Agent tools: an agent runs shell commands as tools and reads their results.
- 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.
- 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.
- Interactive programs: input goes to a running command while its output streams back. Closing stdin tells the command that it has all its input.
- Reconnects: an application reconnects to a process it started earlier and continues to read its output.
- Commands as another user: a command runs as a specific user that exists in the image, or with a specific shell that the image installs.
- Entrypoint checks: the details and logs of the sandbox entrypoint show whether the image's main process started and what it printed.
- Environment configuration: environment variables set once apply to every process started after the update, including PTYs, sessions, and Git commands.
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)| Parameter | Type | Description |
|---|---|---|
timeout | float | Sets how many seconds run() waits. Without it, run() waits until the command exits. When the timeout elapses, reason is "timed_out", exit_code is None, and the command keeps running. |
The response has the following fields:
| Field | Description |
|---|---|
stdout | Holds the output of the stdout channel. |
stderr | Holds the output of the stderr channel. |
output | Holds the output of both channels in recorded 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="daytona", shell="bash")
print(response.output)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.
# 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)| Parameter | Type | Description |
|---|---|---|
env | Mapping[str, str] | Adds or replaces variables. Each KEY=VALUE entry is at most 128 KiB, and env is at most 1 MiB in total. |
unset | Sequence[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("pip uninstall requests", stdin="open")
# Send input to the command
handle.stdin("y\n")
handle.stdin_eof()
wait = handle.wait()
page = handle.logs()
for frame in page.items:
print(f"[{frame.channel}]: {frame.data}")
print(wait.exit_code)| Parameter | Type | Description |
|---|---|---|
stdin | str | Sets 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")
print(handle.id)List processes
List the processes in a sandbox. Filter the list by state, kind, session, name, or PID.
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)Process states
| State | Meaning |
|---|---|
starting | The record exists; the process has not been spawned yet. |
provisioning | Code execution only: the language kernel is being installed or started. Progress is reported on the setup log channel. |
running | The process has a PID and is executing. |
terminal | The process has finished. reason, exitCode, and signal describe how. |
| Reason | Meaning |
|---|---|
exited | Exited on its own; exitCode is set. A code execution that raised an exception exits with 1. |
signaled | Killed by a signal; signal is set. wait reports bare names (TERM, KILL), the API reports SIGTERM, SIGKILL. |
timed_out | The 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. |
lost | The 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. |
failed | The 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.
| Policy | Behavior |
|---|---|
until_cleanup | Kept until an explicit cleanup (DELETE /processes/{id}). Default for processes started with a name; the SDK pty namespace also uses it. |
on_exit_ttl | Kept for 10 minutes after exit, then removed. Default for unnamed processes; the SDK run methods always use it. |
none | Logs are discarded as soon as the process exits. 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 cleanup() is called
handle = sandbox.process.start("npm test", keep_logs="until_cleanup")
handle.wait()
for frame in handle.logs().items:
print(frame.data, end="")
handle.cleanup()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("npm run build")
# 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()| Parameter | Type | Description |
|---|---|---|
signal | SignalName | Sets the signal to send: TERM, KILL, INT, HUP, QUIT, USR1, or USR2. The default is "TERM". |
escalate_after | float | Sends escalate_to if the process is still running after this many seconds. The value is at most 600. |
escalate_to | SignalName | Sets the signal sent after escalate_after. The default is "KILL". |
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("npm run build")
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}")Sessions
A session is a long-lived shell that runs the commands you send to it. Commands in a session run one after another and share the shell state, such as the working directory and environment variables. To run commands in parallel, use separate sessions or background processes.
The SDKs do not create, list, or delete shell sessions. Manage sessions through the API, and pass the session ID to the process methods of the SDK to run commands in a session. Interpreter contexts are sessions with a language kernel instead of a shell.
Create a session
Create a shell session.
curl 'https://toolbox.daytona.com/toolbox/{sandboxId}/sessions' \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{"id": "build-session", "kernel": "shell"}'| Field | Type | Description |
|---|---|---|
kernel | string | Sets the session kernel. python, typescript, and javascript create interpreter contexts. Any other value returns 503 KERNEL_UNAVAILABLE. The default is shell. |
id | string | Sets the session ID. The ID takes letters, digits, ., _, and -, up to 128 characters. If omitted, an ID is generated. |
cwd | string | Sets the working directory of the shell. The default is the home directory of the default user. If that home directory does not exist, the default is the daemon working directory. |
env | object | Sets environment variables applied when the shell starts. |
shell | string | Sets the path or name of the shell. |
login | boolean | Starts a login shell when set to true. The default is false. |
Execute session commands
Run commands in a session. Shell state set by one command is available to the next.
from daytona import Daytona
daytona = Daytona()
sandbox = daytona.v2.create()
session_id = "build-session"
# Shell state persists between commands
sandbox.process.run("cd /tmp && export STAGE=dev", session_id=session_id)
result = sandbox.process.run("pwd && echo $STAGE", session_id=session_id)
print(result.stdout)| Parameter | Type | Description |
|---|---|---|
session_id | str | Runs the command in the session with this ID. process.run and process.start accept this parameter. With session_id, execution_timeout is not sent. |
Get session status
Get the status of a session and the commands that ran in it. The SDKs list its processes; the API also returns the session, with its working directory and whether its shell is active.
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 sessions in a sandbox, including interpreter contexts.
curl 'https://toolbox.daytona.com/toolbox/{sandboxId}/sessions' \
--header 'Authorization: Bearer YOUR_API_KEY'Get session command
Get a command that ran in a session by its process ID.
command = sandbox.process.get("{processId}")
print(f"Command: {command.shell_command}, Exit Code: {command.exit_code}")Delete a session
Delete a session. This stops its shell and removes the records of its commands.
curl 'https://toolbox.daytona.com/toolbox/{sandboxId}/sessions/{sessionId}' \
--request DELETE \
--header 'Authorization: Bearer YOUR_API_KEY'Resource management
Named processes keep their record and logs until you clean them up. Clean up a named process when you no longer need it; cleanup 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")
try:
pass # Do work...
finally:
handle.cleanup() # kills the process if it is still running