Skip to main content
Run commands inside a running sandbox: buffer the output in one call, stream it event by event, drive a shell, or bridge your terminal to an interactive PTY session. See Commands for usage examples.

Typical flow

use microsandbox::Sandbox;

let sb = Sandbox::builder("api").image("python").create().await?;

// Buffered: run and collect everything
let out = sb.exec("python", ["-c", "print('hi')"]).await?;
println!("{}", out.stdout()?);

// Streaming: process output as it arrives
let mut handle = sb.exec_stream("tail", ["-f", "/var/log/app.log"]).await?;
while let Some(event) = handle.recv().await {
    if let microsandbox::ExecEvent::Stdout(chunk) = event {
        print!("{}", String::from_utf8_lossy(&chunk));
    }
}

sb.stop().await?;

Run methods

sb.exec()

async fn exec(
    &self,
    cmd: impl Into<String>,
    args: impl IntoIterator<Item = impl Into<String>>,
) -> MicrosandboxResult<ExecOutput>
let out = sb.exec("python", ["-V"]).await?;
println!("{}", out.stdout()?);
Run a command inside the sandbox and wait for it to complete. Collects all stdout and stderr into memory and returns them along with the exit status. cmd is passed literally to the guest agent: the image ENTRYPOINT is not consulted, and args are not shell-interpreted. For long-running processes or large output, use exec_stream(); for shell syntax like pipes and redirects, use shell().

Parameters

cmdimpl Into<String>
Command to execute, e.g. “python” or “/usr/bin/node”.
argsimpl IntoIterator
Command arguments, e.g. [“-c”, “print(‘hi’)”].

Returns

Collected stdout, stderr, and exit status.

sb.exec_with()

async fn exec_with(
    &self,
    cmd: impl Into<String>,
    f: impl FnOnce(ExecOptionsBuilder) -> ExecOptionsBuilder,
) -> MicrosandboxResult<ExecOutput>
use std::time::Duration;

let out = sb.exec_with("python", |e| e
    .args(["compute.py"])
    .cwd("/app")
    .env("LOG_LEVEL", "debug")
    .timeout(Duration::from_secs(30)))
    .await?;
Run a command with per-execution overrides and wait for completion. The closure receives an ExecOptionsBuilder to set args, working directory, environment variables, user, timeout, resource limits, stdin mode, and TTY allocation. These overrides apply only to this execution and don’t change the sandbox’s defaults.

Parameters

cmdimpl Into<String>
Command to execute.
Configure execution options.

Returns

Collected stdout, stderr, and exit status.

sb.shell()

async fn shell(&self, script: impl Into<String>) -> MicrosandboxResult<ExecOutput>
let out = sb.shell("cat /etc/os-release | grep PRETTY_NAME").await?;
println!("{}", out.stdout()?);
Run a command through the sandbox’s configured shell (default: /bin/sh, set via SandboxBuilder::shell()). The script is run as <shell> -c "<script>", so shell syntax like pipes, redirects, and && chains works.

Parameters

scriptimpl Into<String>
Shell command string, e.g. “ls -la /app && echo done”.

Returns

Collected stdout, stderr, and exit status.

sb.shell_with()

async fn shell_with(
    &self,
    script: impl Into<String>,
    f: impl FnOnce(ExecOptionsBuilder) -> ExecOptionsBuilder,
) -> MicrosandboxResult<ExecOutput>
let out = sb.shell_with("env | sort", |e| e.env("STAGE", "build")).await?;
Run a shell command with per-execution overrides and wait for completion. The -c <script> arguments are prepended to whatever the ExecOptionsBuilder configures, so use the builder for env, cwd, user, timeout, and resource limits rather than for positional args.

Parameters

scriptimpl Into<String>
Shell command string.
Configure execution options.

Returns

Collected stdout, stderr, and exit status.

sb.exec_stream()

async fn exec_stream(
    &self,
    cmd: impl Into<String>,
    args: impl IntoIterator<Item = impl Into<String>>,
) -> MicrosandboxResult<ExecHandle>
use microsandbox::ExecEvent;

let mut handle = sb.exec_stream("tail", ["-f", "/var/log/app.log"]).await?;
while let Some(event) = handle.recv().await {
    match event {
        ExecEvent::Stdout(chunk) => print!("{}", String::from_utf8_lossy(&chunk)),
        ExecEvent::Exited { code } => { println!("exited {code}"); break; }
        _ => {}
    }
}
Run a command with streaming output. Returns an ExecHandle that emits stdout, stderr, started, and exit events as they happen, rather than buffering everything until the command finishes. Use this for long-running processes, large output, or when you need to process output incrementally.

Parameters

cmdimpl Into<String>
Command to execute.
argsimpl IntoIterator
Command arguments.

Returns

Streaming handle for receiving events and controlling the process.

sb.exec_stream_with()

async fn exec_stream_with(
    &self,
    cmd: impl Into<String>,
    f: impl FnOnce(ExecOptionsBuilder) -> ExecOptionsBuilder,
) -> MicrosandboxResult<ExecHandle>
let mut handle = sb.exec_stream_with("python", |e| e.stdin_pipe().tty(true)).await?;
if let Some(stdin) = handle.take_stdin() {
    stdin.write(b"print(2 + 2)\n").await?;
    stdin.close().await?;
}
Streaming execution with per-execution overrides. Enable stdin_pipe() to write to the process’s stdin via ExecHandle::take_stdin(), and tty(true) to allocate a pseudo-terminal for interactive programs like shells, REPLs, or editors.

Parameters

cmdimpl Into<String>
Command to execute.
Configure execution options.

Returns

Streaming handle.

sb.shell_stream()

async fn shell_stream(&self, script: impl Into<String>) -> MicrosandboxResult<ExecHandle>
let mut handle = sb.shell_stream("for i in 1 2 3; do echo $i; sleep 1; done").await?;
let out = handle.collect().await?;
Like shell(), but returns a streaming ExecHandle instead of waiting for completion.

Parameters

scriptimpl Into<String>
Shell command string.

Returns

Streaming handle.

sb.shell_stream_with()

async fn shell_stream_with(
    &self,
    script: impl Into<String>,
    f: impl FnOnce(ExecOptionsBuilder) -> ExecOptionsBuilder,
) -> MicrosandboxResult<ExecHandle>
let mut handle = sb.shell_stream_with("npm run build", |e| e.cwd("/app")).await?;
Run a shell command with per-execution overrides and streaming I/O. As with shell_with(), the -c <script> arguments are prepended to whatever the builder configures.

Parameters

scriptimpl Into<String>
Shell command string.
Configure execution options.

Returns

Streaming handle.

Attach methods

sb.attach()

async fn attach(
    &self,
    cmd: impl Into<String>,
    args: impl IntoIterator<Item = impl Into<String>>,
) -> MicrosandboxResult<i32>
let exit_code = sb.attach("bash", ["-l"]).await?;
Bridge your terminal directly to a process inside the sandbox for a fully interactive PTY session. The host terminal is put into raw mode and its stdin, stdout, and window-size changes are wired to the guest process. Press the detach key (default Ctrl+]) to disconnect without stopping the process; it keeps running in the guest. Returns when the process exits or you detach.

Parameters

cmdimpl Into<String>
Command to run.
argsimpl IntoIterator
Command arguments.

Returns

i32
Exit code of the process, or -1 if you detached before it exited.

sb.attach_with()

async fn attach_with(
    &self,
    cmd: impl Into<String>,
    f: impl FnOnce(AttachOptionsBuilder) -> AttachOptionsBuilder,
) -> MicrosandboxResult<i32>
let exit_code = sb.attach_with("zsh", |a| a
    .env("TERM", "xterm-256color")
    .detach_keys("ctrl-p,ctrl-q"))
    .await?;
Interactive PTY attach with options. The closure receives an AttachOptionsBuilder to set args, environment variables, working directory, user, custom detach keys, and resource limits.

Parameters

cmdimpl Into<String>
Command to run.
Configure attach options.

Returns

i32
Exit code of the process, or -1 if you detached.

sb.attach_shell()

async fn attach_shell(&self) -> MicrosandboxResult<i32>
let exit_code = sb.attach_shell().await?;
Attach to the sandbox’s default shell (configured via SandboxBuilder::shell(), default /bin/sh) with an interactive PTY session.

Returns

i32
Exit code, or -1 if you detached.

ExecOptionsBuilder

Builder for per-execution overrides passed to exec_with(), exec_stream_with(), shell_with(), and shell_stream_with(). Does not change the sandbox’s defaults. Every setter returns Self, so calls chain.

.arg()

fn arg(self, arg: impl Into<String>) -> Self
Append a single command-line argument, e.g. "-la" or "/tmp".

Parameters

argimpl Into<String>
Argument to append.

.args()

fn args(self, args: impl IntoIterator<Item = impl Into<String>>) -> Self
Append multiple command-line arguments.

Parameters

argsimpl IntoIterator
Arguments to append.

.cwd()

fn cwd(self, cwd: impl Into<String>) -> Self
Override the working directory for this command. Overrides the sandbox default set via the builder’s workdir.

Parameters

cwdimpl Into<String>
Absolute path inside the guest.

.user()

fn user(self, user: impl Into<String>) -> Self
Override the guest user for this command.

Parameters

userimpl Into<String>
User name or UID.

.env()

fn env(self, key: impl Into<String>, value: impl Into<String>) -> Self
Set an environment variable for this command. Merged on top of the sandbox-level env vars.

Parameters

keyimpl Into<String>
Variable name.
valueimpl Into<String>
Variable value.

.envs()

fn envs(
    self,
    vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self
Set multiple environment variables for this command at once.

Parameters

varsimpl IntoIterator
Key-value pairs to set.

.timeout()

fn timeout(self, timeout: Duration) -> Self
Kill the process with SIGKILL if it hasn’t exited within this duration.

Parameters

timeoutDuration
Maximum run time before SIGKILL.

.tty()

fn tty(self, enabled: bool) -> Self
Allocate a pseudo-terminal. Enable for interactive programs (shells, editors, top); disable for scripts and batch jobs. When enabled, stdout and stderr are merged at the kernel level inside the guest. Default: false.

Parameters

enabledbool
true to allocate a PTY.

.stdin_null()

fn stdin_null(self) -> Self
Stdin reads from /dev/null. This is the default.

.stdin_pipe()

fn stdin_pipe(self) -> Self
Enable a stdin writer via ExecSink. Use with ExecHandle::take_stdin() on the returned streaming handle to send data to the process.

.stdin_bytes()

fn stdin_bytes(self, data: impl Into<Vec<u8>>) -> Self
Provide fixed bytes as stdin. The process reads them and then sees EOF.

Parameters

dataimpl Into<Vec<u8>>
Bytes fed to the process’s stdin.

.rlimit()

fn rlimit(self, resource: RlimitResource, limit: u64) -> Self
use microsandbox::sandbox::RlimitResource;

let out = sb.exec_with("./worker", |e| e.rlimit(RlimitResource::Nofile, 1024)).await?;
Set a POSIX resource limit with soft equal to hard. Applied via setrlimit() before exec.

Parameters

Which resource to limit.
limitu64
Limit value (soft = hard).

.rlimit_range()

fn rlimit_range(self, resource: RlimitResource, soft: u64, hard: u64) -> Self
Set a resource limit with different soft and hard values. build() errors if soft > hard.

Parameters

Which resource to limit.
softu64
Soft limit (raisable by the process up to hard).
hardu64
Hard ceiling.

.build()

fn build(self) -> MicrosandboxResult<ExecOptions>
Finalize the options. Called automatically by the *_with methods when you use the closure form. Returns an error if any rlimit has soft > hard.

Returns

ExecOptions
Validated execution options.

AttachOptionsBuilder

Builder for interactive attach options passed to attach_with(). Every setter returns Self, so calls chain.

.arg()

fn arg(self, arg: impl Into<String>) -> Self
Append a single command-line argument to the attached command.

Parameters

argimpl Into<String>
Argument to append.

.args()

fn args(self, args: impl IntoIterator<Item = impl Into<String>>) -> Self
Append multiple command-line arguments.

Parameters

argsimpl IntoIterator
Arguments to append.

.cwd()

fn cwd(self, cwd: impl Into<String>) -> Self
Override the working directory for the attached session.

Parameters

cwdimpl Into<String>
Absolute path inside the guest.

.user()

fn user(self, user: impl Into<String>) -> Self
Override the guest user for the attached session.

Parameters

userimpl Into<String>
User name or UID.

.env()

fn env(self, key: impl Into<String>, value: impl Into<String>) -> Self
Set an environment variable for the attached session. Merged on top of the sandbox-level env vars.

Parameters

keyimpl Into<String>
Variable name.
valueimpl Into<String>
Variable value.

.envs()

fn envs(
    self,
    vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self
Set multiple environment variables for the attached session at once.

Parameters

varsimpl IntoIterator
Key-value pairs to set.

.detach_keys()

fn detach_keys(self, keys: impl Into<String>) -> Self
Set the key sequence that detaches from the session without stopping the process. Docker-style syntax: "ctrl-]" (default), "ctrl-p,ctrl-q" for a multi-key sequence, or a single character like "q".

Parameters

keysimpl Into<String>
Detach key specification.

.rlimit()

fn rlimit(self, resource: RlimitResource, limit: u64) -> Self
Set a POSIX resource limit with soft equal to hard for the attached process.

Parameters

Which resource to limit.
limitu64
Limit value (soft = hard).

.rlimit_range()

fn rlimit_range(self, resource: RlimitResource, soft: u64, hard: u64) -> Self
Set a resource limit with different soft and hard values for the attached process. build() errors if soft > hard.

Parameters

Which resource to limit.
softu64
Soft limit.
hardu64
Hard ceiling.

.build()

fn build(self) -> MicrosandboxResult<AttachOptions>
Finalize the options. Called automatically by attach_with(). Returns an error if any rlimit has soft > hard.

Returns

AttachOptions
Validated attach options.

Types

ExecHandle

Returned by exec_stream() · exec_stream_with() · shell_stream() · shell_stream_with()

A handle to a running streaming execution. Receives ExecEvents as the process produces output, and provides control over stdin, signals, and the PTY size.
MethodReturnsDescription
recv()Option<ExecEvent>Receive the next event. None when the session has ended and all output has been delivered.
wait()Result<ExitStatus>Wait for the process to exit, discarding any remaining output.
collect()Result<ExecOutput>Wait for exit and collect all remaining stdout/stderr.
id()StringSession ID for this execution. Can be used to reattach later.
control()ExecControlA cloneable control handle for sending signals and resizes from another task.
take_stdin()Option<ExecSink>Take the stdin writer. Only available if stdin_pipe() was set; returns None after the first call.
signal(signal)Result<()>Send a POSIX signal to the process (e.g. libc::SIGTERM).
kill()Result<()>Send SIGKILL to the process.
resize(rows, cols)Result<()>Resize the PTY for this session.

ExecControl

Returned by ExecHandle.control()

A cloneable, lightweight control handle for a streaming exec session. Lets a task other than the one owning the ExecHandle send signals and PTY resizes. Carries no event stream.
MethodReturnsDescription
id()StringSession ID for this execution.
signal(signal)Result<()>Send a POSIX signal to the process (e.g. libc::SIGTERM).
kill()Result<()>Send SIGKILL to the process.
resize(rows, cols)Result<()>Resize the PTY for this session.

ExecSink

Returned by ExecHandle.take_stdin()

A writer for sending data to a running process’s stdin. Obtained via ExecHandle::take_stdin() after enabling stdin_pipe().
MethodParametersDescription
write(data)data: impl AsRef<[u8]>Write bytes to the process’s stdin.
close()-Close stdin. The process sees EOF on its stdin.

ExecEvent

Yielded by ExecHandle.recv()

An event emitted by a streaming execution.
VariantFieldsDescription
Startedpid: u32The process has started. pid is the guest-side PID.
StdoutBytesA chunk of stdout data. May arrive in arbitrary sizes.
StderrBytesA chunk of stderr data.
Exitedcode: i32The process exited normally. code is the exit code.
FailedExecFailedThe process failed to spawn (binary not found, permission denied, etc.). The user code never ran. Terminal: no further events follow.
StdinErrorExecStdinErrorA stdin write to the child failed (e.g. broken pipe). Non-terminal: the session keeps running and may still emit output and an Exited event.

ExecOutput

Returned by exec() · exec_with() · shell() · shell_with() · ExecHandle.collect()

The result of a completed command execution. Holds the exit status and all captured output.
MethodReturnsDescription
status()ExitStatusExit code and success flag.
stdout()Result<String, FromUtf8Error>Collected stdout decoded as UTF-8. Errors if the output is not valid UTF-8.
stderr()Result<String, FromUtf8Error>Collected stderr decoded as UTF-8.
stdout_bytes()&BytesRaw stdout bytes without decoding.
stderr_bytes()&BytesRaw stderr bytes without decoding.

ExitStatus

Returned by ExecHandle.wait() · ExecOutput.status() · sb.wait() · sb.stop_and_wait()

The exit status of a completed process.
FieldTypeDescription
codei32Exit code. 0 typically means success.
successbooltrue if code is 0.

Rlimit

Configured via rlimit() · rlimit_range()

A POSIX resource limit. Built indirectly by rlimit() and rlimit_range() on the option builders; you rarely construct it by hand.
FieldTypeDescription
resourceRlimitResourceWhich resource to limit.
softu64Soft limit; the process may raise it up to hard.
hardu64Hard ceiling; raising it requires privileges.

RlimitResource

Used by rlimit() · rlimit_range() · AttachOptionsBuilder.rlimit()

POSIX resource limit identifiers. Each maps to an RLIMIT_* constant.
ValueDescription
CpuMax CPU time in seconds (RLIMIT_CPU)
FsizeMax file size in bytes (RLIMIT_FSIZE)
DataMax data segment size (RLIMIT_DATA)
StackMax stack size (RLIMIT_STACK)
CoreMax core file size (RLIMIT_CORE)
RssMax resident set size (RLIMIT_RSS)
NprocMax number of processes (RLIMIT_NPROC)
NofileMax open file descriptors (RLIMIT_NOFILE)
MemlockMax locked memory (RLIMIT_MEMLOCK)
AsMax address space size (RLIMIT_AS)
LocksMax file locks (RLIMIT_LOCKS)
SigpendingMax pending signals (RLIMIT_SIGPENDING)
MsgqueueMax bytes in POSIX message queues (RLIMIT_MSGQUEUE)
NiceMax nice priority (RLIMIT_NICE)
RtprioMax real-time priority (RLIMIT_RTPRIO)
RttimeMax real-time timeout (RLIMIT_RTTIME)