CreateOS Sandbox SDKs
Create, control, and clean up isolated Linux sandboxes from your application. Choose a language in any example; the selection stays in sync across the page.
| Language | Package | Requirements |
|---|---|---|
| TypeScript | @nodeops-createos/sandbox | Node.js 20+, Bun, Deno, edge runtimes, or a browser |
| Go | github.com/NodeOps-app/createos-go-sdk | Go 1.25+ |
| Python | createos-sandbox | Python 3.10+ |
| Rust | createos | Rust and Tokio |
| C# | CreateOS.Sandbox | .NET 8+ |
| Java | sh.createos:createos-java-sdk | Java 17+ |
TypeScript reads CREATEOS_SANDBOX_API_KEY. Go, Python, Rust, C# and Java read
CREATEOS_API_KEY. All six accept the optional
CREATEOS_SANDBOX_BASE_URL override. Set the key in your environment before
running an example.
Install the SDK
npm install @nodeops-createos/sandboxgo get github.com/NodeOps-app/createos-go-sdkpip install createos-sandboxcargo add createos
cargo add tokio --features macros,rt-multi-thread,timedotnet add package CreateOS.Sandbox<!-- Save as pom.xml in your Java project. -->
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>createos-sandbox-demo</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>sh.createos</groupId>
<artifactId>createos-java-sdk</artifactId>
<version>0.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.3</version>
</plugin>
</plugins>
</build>
</project>Paste a language tab into the file shown below and run it inside a project with that SDK installed. For Go, Rust, C# and Java, create a module, Cargo package, .NET console project or Java 17 Maven project first. Each example creates and destroys its own sandbox.
| Language | File | Run |
|---|---|---|
| TypeScript | example.ts | bun example.ts |
| Go | main.go | go run main.go |
| Python | main.py | python main.py |
| Rust | src/main.rs | cargo run |
| C# | Program.cs | dotnet run |
| Java | src/main/java/Main.java | mvn compile exec:java -Dexec.mainClass=Main |
Create a sandbox and run a command
The create call returns a connected sandbox that is ready to accept commands. Always destroy it when the work is complete.
import { createClient } from "@nodeops-createos/sandbox";
const client = createClient();
const sandbox = await client.createSandbox({
shape: "s-1vcpu-1gb",
rootfs: "devbox:1",
});
try {
const response = await sandbox.runCommand("echo", ["Hello from CreateOS"]);
console.log(response.result.stdout);
} finally {
// Release the sandbox even if the command fails.
await sandbox.destroy();
}package main
import (
"context"
"fmt"
"log"
"github.com/NodeOps-app/createos-go-sdk/sandbox"
"github.com/NodeOps-app/createos-go-sdk/structs"
)
func main() {
ctx := context.Background()
client, err := sandbox.NewClient()
if err != nil {
log.Fatal(err)
}
instance, err := client.CreateSandbox(ctx, structs.CreateSandboxRequest{
Shape: "s-1vcpu-1gb",
RootFS: "devbox:1",
})
if err != nil {
log.Fatal(err)
}
// Schedule cleanup for when main returns.
defer instance.Destroy(context.Background())
response, err := instance.RunCommand(ctx, structs.RunCommandRequest{
Command: "echo",
Arguments: []string{"Hello from CreateOS"},
}, structs.ExecOptions{})
if err != nil {
log.Fatal(err)
}
fmt.Print(response.Result.StandardOutput)
}from createos import Client, CreateSandboxRequest, RunCommandRequest
with Client() as client:
sandbox = client.create_sandbox(
CreateSandboxRequest(shape="s-1vcpu-1gb", rootfs="devbox:1")
)
try:
response = sandbox.run_command(
RunCommandRequest(
command="echo", arguments=["Hello from CreateOS"]
)
)
print(response.result.standard_output, end="")
finally:
# Release the sandbox even if the command fails.
sandbox.destroy()use createos::{Client, CreateSandboxRequest, RunCommandRequest};
#[tokio::main]
async fn main() -> createos::Result<()> {
let client = Client::from_env()?;
let sandbox = client.create_sandbox(CreateSandboxRequest {
shape: "s-1vcpu-1gb".into(),
rootfs: Some("devbox:1".into()),
..Default::default()
}).await?;
let result = sandbox.run_command(RunCommandRequest {
command: "echo".into(),
arguments: vec!["Hello from CreateOS".into()],
..Default::default()
}, Default::default()).await;
// Clean up even when the command fails.
let cleanup = sandbox.destroy().await;
print!("{}", result?.result.standard_output);
cleanup?;
Ok(())
}using CreateOS.Sandbox;
var client = new SandboxClient();
var sandbox = await client.CreateSandboxAsync(new CreateSandboxRequest
{
Shape = "s-1vcpu-1gb",
RootFileSystem = "devbox:1"
});
try
{
var response = await sandbox.RunCommandAsync(new RunCommandRequest
{
Command = "echo",
Arguments = ["Hello from CreateOS"]
});
Console.Write(response.Result.StandardOutput);
}
finally
{
// Cleanup must still run if command execution throws.
await sandbox.DestroyAsync(CancellationToken.None);
}import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CreateSandboxRequest;
import sh.createos.model.RunCommandRequest;
public class Main {
public static void main(String[] args) {
CreateOsClient client = CreateOsClient.builder().build();
Sandbox sandbox = client.createSandbox(
CreateSandboxRequest.builder("s-1vcpu-1gb")
.rootFileSystem("devbox:1")
.build());
try {
var response = sandbox.runCommand(
RunCommandRequest.of("echo", "Hello from CreateOS"));
System.out.print(response.result().standardOutput());
} finally {
// Release the sandbox even if the command fails.
sandbox.destroy();
}
}
}Stream command output
Receive output as it is produced instead of waiting for the command to finish. Every tab below is a standalone program.
import { createClient } from "@nodeops-createos/sandbox";
const client = createClient();
const sandbox = await client.createSandbox({
shape: "s-1vcpu-1gb",
rootfs: "devbox:1",
});
try {
const command = "for n in 1 2 3; do echo result-$n; sleep 1; done";
// Handle each stdout event as the command runs.
for await (const event of sandbox.streamCommand("sh", ["-c", command])) {
if (event.type === "stdout") process.stdout.write(event.data);
}
} finally {
await sandbox.destroy();
}package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"github.com/NodeOps-app/createos-go-sdk/sandbox"
"github.com/NodeOps-app/createos-go-sdk/structs"
)
func main() {
ctx := context.Background()
client, err := sandbox.NewClient()
if err != nil {
log.Fatal(err)
}
instance, err := client.CreateSandbox(ctx, structs.CreateSandboxRequest{
Shape: "s-1vcpu-1gb", RootFS: "devbox:1",
})
if err != nil {
log.Fatal(err)
}
defer instance.Destroy(context.Background())
command := "for n in 1 2 3; do echo result-$n; sleep 1; done"
stream, err := instance.StreamCommand(ctx, structs.RunCommandRequest{
Command: "sh", Arguments: []string{"-c", command},
}, structs.ExecOptions{})
if err != nil {
log.Fatal(err)
}
defer stream.Close()
// Receive output incrementally until the stream ends.
for {
event, err := stream.Receive()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
log.Fatal(err)
}
if event.Type == structs.ExecStreamEventStdout {
fmt.Print(event.Data)
}
}
}from createos import (
Client,
CreateSandboxRequest,
ExecStreamEventType,
RunCommandRequest,
)
with Client() as client:
sandbox = client.create_sandbox(
CreateSandboxRequest(shape="s-1vcpu-1gb", rootfs="devbox:1")
)
try:
command = "for n in 1 2 3; do echo result-$n; sleep 1; done"
request = RunCommandRequest(command="sh", arguments=["-c", command])
with sandbox.stream_command(request) as stream:
# Print stdout events as they arrive.
for event in stream:
if event.type is ExecStreamEventType.STDOUT:
print(event.data, end="")
finally:
sandbox.destroy()use createos::{Client, CreateSandboxRequest};
use createos::{ExecStreamEventType, RunCommandRequest};
#[tokio::main]
async fn main() -> createos::Result<()> {
let client = Client::from_env()?;
let sandbox = client.create_sandbox(CreateSandboxRequest {
shape: "s-1vcpu-1gb".into(),
rootfs: Some("devbox:1".into()),
..Default::default()
}).await?;
let result = async {
let mut stream = sandbox.stream_command(RunCommandRequest {
command: "sh".into(),
arguments: vec!["-c".into(), "for n in 1 2 3; do echo result-$n; sleep 1; done".into()],
..Default::default()
}, Default::default()).await?;
// Consume events as they arrive instead of waiting for the full command output.
while let Some(event) = stream.next().await? {
if event.kind.as_str() == ExecStreamEventType::STDOUT {
print!("{}", event.data);
}
}
Ok::<_, createos::Error>(())
}.await;
let cleanup = sandbox.destroy().await;
result?;
cleanup?;
Ok(())
}using CreateOS.Sandbox;
var client = new SandboxClient();
var sandbox = await client.CreateSandboxAsync(new CreateSandboxRequest
{
Shape = "s-1vcpu-1gb", RootFileSystem = "devbox:1"
});
try
{
await foreach (var item in sandbox.StreamCommandAsync(new RunCommandRequest
{
Command = "sh",
Arguments = ["-c", "for n in 1 2 3; do echo result-$n; sleep 1; done"]
}))
{
// Other event types include stderr, exit, error, and heartbeat.
if (item.Type == ExecStreamEventType.Stdout)
Console.Write(item.Data);
}
}
finally
{
await sandbox.DestroyAsync(CancellationToken.None);
}import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CommandStreamEvent;
import sh.createos.model.CreateSandboxRequest;
import sh.createos.model.RunCommandRequest;
public class Main {
public static void main(String[] args) throws Exception {
CreateOsClient client = CreateOsClient.builder().build();
Sandbox sandbox = client.createSandbox(
CreateSandboxRequest.builder("s-1vcpu-1gb")
.rootFileSystem("devbox:1")
.build());
try {
try (var stream = sandbox.streamCommand(RunCommandRequest.of(
"sh", "-c", "for n in 1 2 3; do echo result-$n; sleep 1; done"))) {
// Read stdout events as the command produces them.
for (var event = stream.receive(); event != null; event = stream.receive()) {
if (event.type() == CommandStreamEvent.Type.STDOUT) {
System.out.print(event.data());
}
}
}
} finally {
sandbox.destroy();
}
}
}Upload files
Upload a file without shell escaping.
import { createClient } from "@nodeops-createos/sandbox";
const client = createClient();
const sandbox = await client.createSandbox({
shape: "s-1vcpu-1gb",
rootfs: "devbox:1",
});
try {
// File APIs transfer content without shell escaping.
await sandbox.files.upload("/workspace/hello.txt", "Hello from TypeScript");
const file = await sandbox.files.download("/workspace/hello.txt");
console.log(new TextDecoder().decode(file));
} finally {
await sandbox.destroy();
}package main
import (
"context"
"fmt"
"io"
"log"
"strings"
"github.com/NodeOps-app/createos-go-sdk/sandbox"
"github.com/NodeOps-app/createos-go-sdk/structs"
)
func main() {
ctx := context.Background()
client, err := sandbox.NewClient()
if err != nil {
log.Fatal(err)
}
instance, err := client.CreateSandbox(ctx, structs.CreateSandboxRequest{
Shape: "s-1vcpu-1gb", RootFS: "devbox:1",
})
if err != nil {
log.Fatal(err)
}
defer instance.Destroy(context.Background())
// Upload bytes directly instead of building a shell command.
content := strings.NewReader("Hello from Go")
if err := instance.Files().Upload(ctx, "/workspace/hello.txt", content); err != nil {
log.Fatal(err)
}
file, err := instance.Files().Download(ctx, "/workspace/hello.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
contents, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(contents))
}from createos import Client, CreateSandboxRequest
with Client() as client:
sandbox = client.create_sandbox(
CreateSandboxRequest(shape="s-1vcpu-1gb", rootfs="devbox:1")
)
try:
# Upload content directly without shell escaping.
sandbox.files.upload("/workspace/hello.txt", b"Hello from Python")
with sandbox.files.download("/workspace/hello.txt") as file:
print(file.read().decode())
finally:
sandbox.destroy()use createos::{Client, CreateSandboxRequest};
use createos::RequestOptions;
#[tokio::main]
async fn main() -> createos::Result<()> {
let client = Client::from_env()?;
let sandbox = client.create_sandbox(CreateSandboxRequest {
shape: "s-1vcpu-1gb".into(),
rootfs: Some("devbox:1".into()),
..Default::default()
}).await?;
let result = async {
let files = sandbox.files();
// Transfer bytes directly; no shell quoting is needed.
files.upload("/workspace/hello.txt", b"Hello from Rust".to_vec(),
&RequestOptions::default()).await?;
let response = files.download("/workspace/hello.txt",
&RequestOptions::default()).await?;
println!("{}", String::from_utf8_lossy(&response.bytes().await?));
Ok::<_, createos::Error>(())
}.await;
let cleanup = sandbox.destroy().await;
result?;
cleanup?;
Ok(())
}using System.Text;
using CreateOS.Sandbox;
var client = new SandboxClient();
var sandbox = await client.CreateSandboxAsync(new CreateSandboxRequest
{
Shape = "s-1vcpu-1gb", RootFileSystem = "devbox:1"
});
try
{
// Dispose both streams so the upload and download can release their connections.
await using (var source = new MemoryStream(Encoding.UTF8.GetBytes("Hello from C#")))
await sandbox.Files.UploadAsync("/workspace/hello.txt", source);
await using var file = await sandbox.Files.DownloadAsync("/workspace/hello.txt");
using var reader = new StreamReader(file);
Console.WriteLine(await reader.ReadToEndAsync());
}
finally
{
await sandbox.DestroyAsync(CancellationToken.None);
}import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CreateSandboxRequest;
public class Main {
public static void main(String[] args) throws Exception {
CreateOsClient client = CreateOsClient.builder().build();
Sandbox sandbox = client.createSandbox(
CreateSandboxRequest.builder("s-1vcpu-1gb")
.rootFileSystem("devbox:1")
.build());
try {
// Upload bytes directly; no shell quoting is needed.
byte[] content = "Hello from Java".getBytes(StandardCharsets.UTF_8);
sandbox.files().upload("/workspace/hello.txt", new ByteArrayInputStream(content));
try (InputStream file = sandbox.files().download("/workspace/hello.txt")) {
System.out.println(new String(file.readAllBytes(), StandardCharsets.UTF_8));
}
} finally {
sandbox.destroy();
}
}
}Publish a live preview
Start a service and generate its public URL. Create the sandbox with ingress enabled before running this example.
import { createClient } from "@nodeops-createos/sandbox";
const client = createClient();
const sandbox = await client.createSandbox({
shape: "s-1vcpu-1gb",
rootfs: "devbox:1",
// Public preview URLs require ingress when the sandbox is created.
ingress_enabled: true,
});
try {
await sandbox.processes.create({
cmd: "python3",
args: ["-m", "http.server", "8080", "--bind", "0.0.0.0"],
});
await sandbox.waitForPortReady(8080);
console.log(sandbox.previewUrl(8080));
} finally {
await sandbox.destroy();
}package main
import (
"context"
"fmt"
"log"
"time"
"github.com/NodeOps-app/createos-go-sdk/sandbox"
"github.com/NodeOps-app/createos-go-sdk/structs"
)
func main() {
ctx := context.Background()
client, err := sandbox.NewClient()
if err != nil {
log.Fatal(err)
}
// Enable ingress before starting the service.
instance, err := client.CreateSandbox(ctx, structs.CreateSandboxRequest{
Shape: "s-1vcpu-1gb", RootFS: "devbox:1", IngressEnabled: true,
})
if err != nil {
log.Fatal(err)
}
defer instance.Destroy(context.Background())
_, err = instance.Processes().Create(ctx, structs.ManagedProcessCreateRequest{
Command: "python3",
Arguments: []string{"-m", "http.server", "8080", "--bind", "0.0.0.0"},
})
if err != nil {
log.Fatal(err)
}
if err := instance.WaitForPort(ctx, "127.0.0.1", 8080, 15*time.Second); err != nil {
log.Fatal(err)
}
previewURL, err := instance.PreviewURL(8080)
if err != nil {
log.Fatal(err)
}
fmt.Println(previewURL)
}from createos import (
Client,
CreateSandboxRequest,
ManagedProcessCreateRequest,
)
with Client() as client:
sandbox = client.create_sandbox(
CreateSandboxRequest(
# Enable ingress before starting the service.
shape="s-1vcpu-1gb", rootfs="devbox:1", ingress_enabled=True
)
)
try:
sandbox.processes.create(
ManagedProcessCreateRequest(
command="python3",
arguments=["-m", "http.server", "8080", "--bind", "0.0.0.0"],
)
)
sandbox.wait_for_port(8080, timeout=15)
print(sandbox.preview_url(8080))
finally:
sandbox.destroy()use createos::Client;
use createos::{CreateSandboxRequest, ManagedProcessCreateRequest};
use std::time::Duration;
#[tokio::main]
async fn main() -> createos::Result<()> {
let client = Client::from_env()?;
let sandbox = client.create_sandbox(CreateSandboxRequest {
shape: "s-1vcpu-1gb".into(),
rootfs: Some("devbox:1".into()),
// Public preview URLs require ingress at creation time.
ingress_enabled: true,
..Default::default()
}).await?;
let result = async {
sandbox.processes().create(ManagedProcessCreateRequest {
command: "python3".into(),
arguments: vec!["-m".into(), "http.server".into(), "8080".into(),
"--bind".into(), "0.0.0.0".into()],
..Default::default()
}).await?;
sandbox.wait_for_port(None, 8080, Duration::from_secs(15)).await?;
println!("{}", sandbox.preview_url(8080)?);
Ok::<_, createos::Error>(())
}.await;
let cleanup = sandbox.destroy().await;
result?;
cleanup?;
Ok(())
}using CreateOS.Sandbox;
var client = new SandboxClient();
var sandbox = await client.CreateSandboxAsync(new CreateSandboxRequest
{
// Enable ingress before starting the service.
Shape = "s-1vcpu-1gb", RootFileSystem = "devbox:1", IngressEnabled = true
});
try
{
await sandbox.Processes.CreateAsync(new ManagedProcessCreateRequest
{
Command = "python3",
Arguments = ["-m", "http.server", "8080", "--bind", "0.0.0.0"]
});
await sandbox.WaitForPortAsync(8080, timeout: TimeSpan.FromSeconds(15));
Console.WriteLine(sandbox.GetPreviewUri(8080));
}
finally
{
await sandbox.DestroyAsync(CancellationToken.None);
}import java.time.Duration;
import java.util.List;
import java.util.Map;
import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CreateProcessRequest;
import sh.createos.model.CreateSandboxRequest;
public class Main {
public static void main(String[] args) throws Exception {
CreateOsClient client = CreateOsClient.builder().build();
// Enable ingress when creating the sandbox for a public preview URL.
Sandbox sandbox = client.createSandbox(
CreateSandboxRequest.builder("s-1vcpu-1gb")
.rootFileSystem("devbox:1")
.ingressEnabled(true)
.build());
try {
sandbox.processes().create(new CreateProcessRequest(
"python3", List.of("-m", "http.server", "8080", "--bind", "0.0.0.0"),
null, Map.of(), null));
sandbox.waitForPort("127.0.0.1", 8080, Duration.ofSeconds(15));
System.out.println(sandbox.previewUrl(8080));
} finally {
sandbox.destroy();
}
}
}Run managed processes
Keep a stable process ID, reconnect to output, send input, and wait for the entire process tree.
import { createClient } from "@nodeops-createos/sandbox";
const client = createClient();
const sandbox = await client.createSandbox({
shape: "s-1vcpu-1gb",
rootfs: "devbox:1",
});
try {
const process = await sandbox.processes.create({
cmd: "sh",
args: ["-c", "echo finished"],
});
// Wait for the process and any children it started.
const result = await sandbox.processes.wait(process.process_id, {
scope: "tree",
});
console.log(result.exit_code);
} finally {
await sandbox.destroy();
}package main
import (
"context"
"fmt"
"log"
"github.com/NodeOps-app/createos-go-sdk/sandbox"
"github.com/NodeOps-app/createos-go-sdk/structs"
)
func main() {
ctx := context.Background()
client, err := sandbox.NewClient()
if err != nil {
log.Fatal(err)
}
instance, err := client.CreateSandbox(ctx, structs.CreateSandboxRequest{
Shape: "s-1vcpu-1gb", RootFS: "devbox:1",
})
if err != nil {
log.Fatal(err)
}
defer instance.Destroy(context.Background())
process, err := instance.Processes().Create(ctx, structs.ManagedProcessCreateRequest{
Command: "sh", Arguments: []string{"-c", "echo finished"},
})
if err != nil {
log.Fatal(err)
}
// Tree scope waits for child processes too.
result, err := instance.Processes().Wait(ctx, process.ProcessID,
structs.ManagedProcessWaitOptions{Scope: structs.ManagedProcessWaitScopeTree},
)
if err != nil {
log.Fatal(err)
}
fmt.Println(*result.ExitCode)
}from createos import (
Client,
CreateSandboxRequest,
ManagedProcessCreateRequest,
ManagedProcessWaitOptions,
ManagedProcessWaitScope,
)
with Client() as client:
sandbox = client.create_sandbox(
CreateSandboxRequest(shape="s-1vcpu-1gb", rootfs="devbox:1")
)
try:
process = sandbox.processes.create(
ManagedProcessCreateRequest(
command="sh", arguments=["-c", "echo finished"]
)
)
# Tree scope includes child processes started by this command.
result = sandbox.processes.wait(
process.process_id,
ManagedProcessWaitOptions(scope=ManagedProcessWaitScope.TREE),
)
print(result.exit_code)
finally:
sandbox.destroy()use createos::{Client, CreateSandboxRequest};
use createos::{ManagedProcessCreateRequest, ManagedProcessWaitOptions,
ManagedProcessWaitScope};
use std::time::Duration;
#[tokio::main]
async fn main() -> createos::Result<()> {
let client = Client::from_env()?;
let sandbox = client.create_sandbox(CreateSandboxRequest {
shape: "s-1vcpu-1gb".into(),
rootfs: Some("devbox:1".into()),
..Default::default()
}).await?;
let result = async {
let process = sandbox.processes().create(ManagedProcessCreateRequest {
command: "sh".into(),
arguments: vec!["-c".into(), "echo finished".into()],
..Default::default()
}).await?;
let result = sandbox.processes().wait(&process.process_id,
ManagedProcessWaitOptions {
// Wait for child processes too, not only the leader.
scope: Some(ManagedProcessWaitScope::TREE.into()),
wait_timeout: Some(Duration::from_secs(30)),
..Default::default()
}).await?;
println!("{:?}", result.exit_code);
Ok::<_, createos::Error>(())
}.await;
let cleanup = sandbox.destroy().await;
result?;
cleanup?;
Ok(())
}using CreateOS.Sandbox;
var client = new SandboxClient();
var sandbox = await client.CreateSandboxAsync(new CreateSandboxRequest
{
Shape = "s-1vcpu-1gb", RootFileSystem = "devbox:1"
});
try
{
var process = await sandbox.Processes.CreateAsync(new ManagedProcessCreateRequest
{
Command = "sh", Arguments = ["-c", "echo finished"]
});
var result = await sandbox.Processes.WaitAsync(process.ProcessId,
new ManagedProcessWaitOptions
{
// The tree scope includes children started by the process.
Scope = ManagedProcessWaitScope.Tree,
WaitTimeout = TimeSpan.FromSeconds(30)
});
Console.WriteLine(result.ExitCode);
}
finally
{
await sandbox.DestroyAsync(CancellationToken.None);
}import java.time.Duration;
import java.util.List;
import java.util.Map;
import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CreateProcessRequest;
import sh.createos.model.CreateSandboxRequest;
public class Main {
public static void main(String[] args) throws Exception {
CreateOsClient client = CreateOsClient.builder().build();
Sandbox sandbox = client.createSandbox(
CreateSandboxRequest.builder("s-1vcpu-1gb")
.rootFileSystem("devbox:1")
.build());
try {
var process = sandbox.processes().create(new CreateProcessRequest(
"sh", List.of("-c", "echo finished"), null, Map.of(), null));
// Tree scope waits for child processes as well as the leader.
var result = sandbox.processes().waitFor(
process.processId(), "tree", Duration.ofSeconds(30));
System.out.println(result.exitCode());
} finally {
sandbox.destroy();
}
}
}Spawn an interactive terminal
Create a PTY-backed shell, resize its terminal, send commands, and reconnect to its retained output after it exits.
import { createClient } from "@nodeops-createos/sandbox";
const client = createClient();
const sandbox = await client.createSandbox({
shape: "s-1vcpu-1gb",
rootfs: "devbox:1",
});
try {
// PTY mode supports terminal input and resizing.
const terminal = await sandbox.processes.create({
cwd: "/workspace",
pty: { rows: 24, cols: 80 },
});
await sandbox.processes.resize(terminal.process_id, {
rows: 32,
cols: 100,
});
await sandbox.processes.input(
terminal.process_id,
"echo 'CreateOS terminal ready'; uname -s; pwd; exit\n",
);
const result = await sandbox.processes.wait(terminal.process_id, {
scope: "tree",
});
if (result.exit_code !== 0) {
throw new Error(`Terminal exited with code ${result.exit_code}`);
}
for await (const event of sandbox.processes.connect(terminal.process_id)) {
if (event.type === "data" && event.stream === "pty") {
process.stdout.write(event.data);
}
}
} finally {
await sandbox.destroy();
}package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"github.com/NodeOps-app/createos-go-sdk/sandbox"
"github.com/NodeOps-app/createos-go-sdk/structs"
)
func main() {
ctx := context.Background()
client, err := sandbox.NewClient()
if err != nil {
log.Fatal(err)
}
instance, err := client.CreateSandbox(ctx, structs.CreateSandboxRequest{
Shape: "s-1vcpu-1gb", RootFS: "devbox:1",
})
if err != nil {
log.Fatal(err)
}
defer instance.Destroy(context.Background())
// PTY mode supports terminal input and resizing.
terminal, err := instance.Processes().Create(ctx, structs.ManagedProcessCreateRequest{
WorkingDirectory: "/workspace",
PTY: &structs.PTYSize{Rows: 24, Cols: 80},
})
if err != nil {
log.Fatal(err)
}
if err := instance.Processes().Resize(ctx, terminal.ProcessID,
structs.PTYSize{Rows: 32, Cols: 100}); err != nil {
log.Fatal(err)
}
_, err = instance.Processes().Input(ctx, terminal.ProcessID,
"echo 'CreateOS terminal ready'; uname -s; pwd; exit\n")
if err != nil {
log.Fatal(err)
}
result, err := instance.Processes().Wait(ctx, terminal.ProcessID,
structs.ManagedProcessWaitOptions{Scope: structs.ManagedProcessWaitScopeTree},
)
if err != nil {
log.Fatal(err)
}
if result.ExitCode == nil || *result.ExitCode != 0 {
log.Fatalf("terminal exited with code %v", result.ExitCode)
}
stream, err := instance.Processes().Connect(ctx, terminal.ProcessID,
structs.ManagedProcessConnectOptions{})
if err != nil {
log.Fatal(err)
}
defer stream.Close()
for {
event, err := stream.Receive()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
log.Fatal(err)
}
if event.Type == structs.ManagedProcessConnectEventData &&
event.Stream == structs.ManagedProcessStreamPTY {
fmt.Print(string(event.Data))
}
}
}from createos import (
Client,
CreateSandboxRequest,
ManagedProcessConnectEventType,
ManagedProcessCreateRequest,
ManagedProcessStream,
ManagedProcessWaitOptions,
ManagedProcessWaitScope,
PTYSize,
)
with Client() as client:
sandbox = client.create_sandbox(
CreateSandboxRequest(shape="s-1vcpu-1gb", rootfs="devbox:1")
)
try:
processes = sandbox.processes
# PTY mode supports terminal input and resizing.
terminal = processes.create(
ManagedProcessCreateRequest(
working_directory="/workspace",
pty=PTYSize(rows=24, cols=80),
)
)
processes.resize(terminal.process_id, PTYSize(rows=32, cols=100))
processes.input(
terminal.process_id,
"echo 'CreateOS terminal ready'; uname -s; pwd; exit\n",
)
result = processes.wait(
terminal.process_id,
ManagedProcessWaitOptions(scope=ManagedProcessWaitScope.TREE),
)
if result.exit_code != 0:
raise RuntimeError(
f"Terminal exited with code {result.exit_code}"
)
with processes.connect(terminal.process_id) as stream:
for event in stream:
if (
event.type is ManagedProcessConnectEventType.DATA
and event.stream is ManagedProcessStream.PTY
):
print(event.data.decode(errors="replace"), end="")
finally:
sandbox.destroy()use createos::{Client, CreateSandboxRequest};
use createos::{ManagedProcessConnectEventType, ManagedProcessConnectOptions,
ManagedProcessCreateRequest, ManagedProcessStream, ManagedProcessWaitOptions,
ManagedProcessWaitScope, PtySize};
#[tokio::main]
async fn main() -> createos::Result<()> {
let client = Client::from_env()?;
let sandbox = client.create_sandbox(CreateSandboxRequest {
shape: "s-1vcpu-1gb".into(),
rootfs: Some("devbox:1".into()),
..Default::default()
}).await?;
let result = async {
let processes = sandbox.processes();
let terminal = processes.create(ManagedProcessCreateRequest {
working_directory: Some("/workspace".into()),
// PTY mode gives the shell terminal input and resize support.
pty: Some(PtySize { rows: 24, cols: 80 }),
..Default::default()
}).await?;
processes.resize(&terminal.process_id, PtySize { rows: 32, cols: 100 }).await?;
processes.input(&terminal.process_id,
"echo 'CreateOS terminal ready'; uname -s; pwd; exit\n").await?;
let result = processes.wait(&terminal.process_id, ManagedProcessWaitOptions {
scope: Some(ManagedProcessWaitScope::TREE.into()), ..Default::default()
}).await?;
if result.exit_code != Some(0) { return Err(createos::Error::Protocol("terminal failed".into())); }
let mut stream = processes.connect(&terminal.process_id,
ManagedProcessConnectOptions::default()).await?;
while let Some(event) = stream.next().await? {
if event.kind.as_str() == ManagedProcessConnectEventType::DATA
&& event.stream.as_str() == ManagedProcessStream::PTY {
print!("{}", String::from_utf8_lossy(&event.data));
}
}
Ok::<_, createos::Error>(())
}.await;
let cleanup = sandbox.destroy().await;
result?;
cleanup?;
Ok(())
}using System.Text;
using CreateOS.Sandbox;
var client = new SandboxClient();
var sandbox = await client.CreateSandboxAsync(new CreateSandboxRequest
{
Shape = "s-1vcpu-1gb", RootFileSystem = "devbox:1"
});
try
{
var processes = sandbox.Processes;
var terminal = await processes.CreateAsync(new ManagedProcessCreateRequest
{
// A PTY keeps terminal output available for replay after exit.
WorkingDirectory = "/workspace", Pty = new PtySize(24, 80)
});
await processes.ResizeAsync(terminal.ProcessId, new PtySize(32, 100));
await processes.InputAsync(terminal.ProcessId,
"echo 'CreateOS terminal ready'; uname -s; pwd; exit\n");
var result = await processes.WaitAsync(terminal.ProcessId,
new ManagedProcessWaitOptions { Scope = ManagedProcessWaitScope.Tree });
if (result.ExitCode != 0) throw new InvalidOperationException("terminal failed");
await foreach (var item in processes.ConnectAsync(terminal.ProcessId))
{
if (item.Type == ManagedProcessConnectEventType.Data &&
item.Stream == ManagedProcessStream.Pty && item.Data is not null)
Console.Write(Encoding.UTF8.GetString(item.Data));
}
}
finally
{
await sandbox.DestroyAsync(CancellationToken.None);
}import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CreateProcessRequest;
import sh.createos.model.CreateSandboxRequest;
import sh.createos.model.PtySize;
public class Main {
public static void main(String[] args) throws Exception {
CreateOsClient client = CreateOsClient.builder().build();
Sandbox sandbox = client.createSandbox(
CreateSandboxRequest.builder("s-1vcpu-1gb")
.rootFileSystem("devbox:1")
.build());
try {
// PTY mode supports input, resizing and output replay after exit.
var processes = sandbox.processes();
var terminal = processes.create(new CreateProcessRequest(
null, List.of(), "/workspace", Map.of(), new PtySize(24, 80)));
processes.resize(terminal.processId(), new PtySize(32, 100));
processes.input(terminal.processId(),
"echo 'CreateOS terminal ready'; uname -s; pwd; exit\n");
var result = processes.waitFor(terminal.processId(), "tree", Duration.ofSeconds(30));
if (result.exitCode() == null || result.exitCode() != 0) {
throw new IllegalStateException("terminal failed");
}
try (var stream = processes.connect(terminal.processId(), 0)) {
for (var event = stream.receive(); event != null; event = stream.receive()) {
if ("data".equals(event.type()) && "pty".equals(event.stream())) {
System.out.print(new String(event.data(), StandardCharsets.UTF_8));
}
}
}
} finally {
sandbox.destroy();
}
}
}Automate a cloud desktop
Open the NodeOps website in a graphical cloud browser and capture a validated
PNG screenshot. This example requires the desktop:1 image.
import { createClient } from "@nodeops-createos/sandbox";
const client = createClient();
const sandbox = await client.createSandbox({
shape: "s-2vcpu-4gb",
rootfs: "desktop:1",
});
try {
let screen;
// Wait for the desktop service before opening the browser.
for (let attempt = 0; attempt < 60; attempt++) {
screen = await sandbox.computer.screen({ screenId: "screen-0" })
.catch(() => undefined);
if (screen) break;
await new Promise((resolve) => setTimeout(resolve, 2_000));
}
if (!screen) throw new Error("Desktop did not become ready");
const target = "https://createos.sh";
await sandbox.computer.open(target, { screenId: "screen-0" });
await new Promise((resolve) => setTimeout(resolve, 3_000));
const screenshot = await sandbox.computer.screenshot({
screenId: "screen-0",
timeoutMs: 45_000,
});
const view = new DataView(screenshot);
const width = view.getUint32(16, false);
const height = view.getUint32(20, false);
console.log(`Opened ${target} and captured a ${width}x${height} screenshot`);
} finally {
await sandbox.destroy();
}package main
import (
"context"
"fmt"
"image/png"
"log"
"time"
"github.com/NodeOps-app/createos-go-sdk/sandbox"
"github.com/NodeOps-app/createos-go-sdk/structs"
)
func main() {
ctx := context.Background()
client, err := sandbox.NewClient()
if err != nil {
log.Fatal(err)
}
instance, err := client.CreateSandbox(ctx, structs.CreateSandboxRequest{
Shape: "s-2vcpu-4gb", RootFS: "desktop:1",
})
if err != nil {
log.Fatal(err)
}
defer instance.Destroy(context.Background())
options := structs.ComputerScreenOptions{ScreenID: structs.ComputerScreen0}
// The desktop service may start after the sandbox becomes available.
for attempt := 0; attempt < 60; attempt++ {
_, err = instance.Computer().Screen(ctx, options)
if err == nil {
break
}
time.Sleep(2 * time.Second)
}
if err != nil {
log.Fatal("desktop did not become ready: ", err)
}
target := "https://createos.sh"
if err := instance.Computer().Open(ctx,
structs.ComputerOpenRequest{Target: target}, options); err != nil {
log.Fatal(err)
}
time.Sleep(3 * time.Second)
screenshot, err := instance.Computer().Screenshot(ctx, structs.ComputerScreenshotOptions{
ComputerScreenOptions: options,
})
if err != nil {
log.Fatal(err)
}
defer screenshot.Close()
image, err := png.DecodeConfig(screenshot)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Opened %s and captured a %dx%d screenshot\n",
target, image.Width, image.Height)
}import struct
import time
from createos import (
APIError,
Client,
ComputerOpenRequest,
ComputerScreenID,
ComputerScreenOptions,
ComputerScreenshotOptions,
CreateSandboxRequest,
)
with Client() as client:
sandbox = client.create_sandbox(
CreateSandboxRequest(shape="s-2vcpu-4gb", rootfs="desktop:1")
)
try:
computer = sandbox.computer
options = ComputerScreenOptions(screen_id=ComputerScreenID.SCREEN_0)
# Wait for the desktop service before opening the browser.
for _ in range(60):
try:
computer.screen(options)
break
except APIError:
time.sleep(2)
else:
raise TimeoutError("Desktop did not become ready")
target = "https://nodeops.network"
computer.open(ComputerOpenRequest(target=target), options)
time.sleep(3)
with computer.screenshot(
ComputerScreenshotOptions(
screen_id=ComputerScreenID.SCREEN_0, timeout=45
)
) as screenshot:
payload = screenshot.read()
# PNG stores width and height as big-endian integers in the IHDR chunk.
width, height = struct.unpack(">II", payload[16:24])
print(f"Opened {target} and captured a {width}x{height} screenshot")
finally:
sandbox.destroy()use createos::Client;
use createos::{ComputerOpenRequest, ComputerScreenId, ComputerScreenOptions,
ComputerScreenshotOptions, CreateSandboxRequest};
use std::time::Duration;
#[tokio::main]
async fn main() -> createos::Result<()> {
let client = Client::from_env()?;
let sandbox = client.create_sandbox(CreateSandboxRequest {
shape: "s-2vcpu-4gb".into(), rootfs: Some("desktop:1".into()),
..Default::default()
}).await?;
let result = async {
let computer = sandbox.computer();
let screen = ComputerScreenOptions {
screen_id: Some(ComputerScreenId::SCREEN_0.into()),
..Default::default()
};
for attempt in 0..60 {
if computer.screen(screen.clone()).await.is_ok() { break; }
if attempt == 59 { return Err(createos::Error::Protocol("desktop not ready".into())); }
tokio::time::sleep(Duration::from_secs(2)).await;
}
let target = "https://createos.sh";
computer.open(ComputerOpenRequest { target: target.into() }, screen.clone()).await?;
tokio::time::sleep(Duration::from_secs(3)).await;
let png = computer.screenshot(ComputerScreenshotOptions {
screen, ..Default::default()
}).await?.bytes().await?;
// Read dimensions from the PNG header after checking its signature.
if !png.starts_with(b"\x89PNG\r\n\x1a\n") || png.len() < 24 {
return Err(createos::Error::Protocol("invalid PNG".into()));
}
let width = u32::from_be_bytes(png[16..20].try_into().unwrap());
let height = u32::from_be_bytes(png[20..24].try_into().unwrap());
println!("Opened {target} and captured a {width}x{height} screenshot");
Ok::<_, createos::Error>(())
}.await;
let cleanup = sandbox.destroy().await;
result?;
cleanup?;
Ok(())
}using System.Buffers.Binary;
using CreateOS.Sandbox;
var client = new SandboxClient();
var sandbox = await client.CreateSandboxAsync(new CreateSandboxRequest
{
Shape = "s-2vcpu-4gb", RootFileSystem = "desktop:1"
});
try
{
var screen = new ComputerScreenOptions { ScreenId = ComputerScreenId.Screen0 };
using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(2));
while (true)
{
try { await sandbox.Computer.GetScreenAsync(screen, timeout.Token); break; }
catch (Exception) when (!timeout.IsCancellationRequested)
{
await Task.Delay(2000, timeout.Token);
}
}
const string target = "https://createos.sh";
await sandbox.Computer.OpenAsync(new ComputerOpenRequest(target), screen);
await Task.Delay(3000);
await using var png = await sandbox.Computer.ScreenshotAsync(
new ComputerScreenshotOptions { ScreenId = ComputerScreenId.Screen0 });
var header = new byte[24];
await png.ReadExactlyAsync(header);
// PNG dimensions are big-endian values in the IHDR chunk.
if (!header.AsSpan(0, 8).SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }))
throw new InvalidDataException("invalid PNG");
var width = BinaryPrimitives.ReadInt32BigEndian(header.AsSpan(16, 4));
var height = BinaryPrimitives.ReadInt32BigEndian(header.AsSpan(20, 4));
Console.WriteLine($"Opened {target} and captured a {width}x{height} screenshot");
}
finally
{
await sandbox.DestroyAsync(CancellationToken.None);
}import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.time.Duration;
import javax.imageio.ImageIO;
import sh.createos.CreateOsClient;
import sh.createos.Sandbox;
import sh.createos.model.CreateSandboxRequest;
import sh.createos.model.ScreenOptions;
import sh.createos.model.ScreenshotOptions;
public class Main {
public static void main(String[] args) throws Exception {
CreateOsClient client = CreateOsClient.builder().build();
Sandbox sandbox = client.createSandbox(
CreateSandboxRequest.builder("s-2vcpu-4gb")
.rootFileSystem("desktop:1")
.build());
try {
var computer = sandbox.computer();
var screen = new ScreenOptions("screen-0");
// Wait for the desktop service before opening the browser.
boolean ready = false;
for (int attempt = 0; attempt < 60; attempt++) {
try {
computer.screen(screen);
ready = true;
break;
} catch (RuntimeException exception) {
Thread.sleep(Duration.ofSeconds(2).toMillis());
}
}
if (!ready) throw new IllegalStateException("desktop not ready");
String target = "https://createos.sh";
computer.open(target, screen);
Thread.sleep(Duration.ofSeconds(3).toMillis());
try (InputStream png = computer.screenshot(
new ScreenshotOptions("screen-0", null, null, null, null, null))) {
BufferedImage image = ImageIO.read(png);
if (image == null) throw new IllegalStateException("invalid PNG");
System.out.printf("Opened %s and captured a %dx%d screenshot%n",
target, image.getWidth(), image.getHeight());
}
} finally {
sandbox.destroy();
}
}
}Source and API reference
- TypeScript SDK on GitHub
- TypeScript API reference
- Go SDK on GitHub
- Go API reference
- Python SDK on GitHub
- Python API reference
- Rust SDK and runnable examples
- Rust API reference
- C# SDK and runnable examples
- Java SDK and runnable examples
- Java SDK on Maven Central
Related
- Limits & defaults for what
createSandboxcan ask for on each plan. - REST API if you are using another language; every SDK call is one documented endpoint.
- Packages: @nodeops-createos/sandbox on npm · Python SDK on GitHub · source and issues: GitHub. Provider-agnostic use through ComputeSDK (
@computesdk/createos-sandbox).