OpenSandbox

Ruby SDK for the open-sandbox.ai API — manage isolated container sandboxes for secure code execution.

Gem Version CI

Installation

Add to your Gemfile:

gem "open_sandbox", path: "../open_sandbox"   # local development
# or, once published:
gem "open_sandbox", "~> 0.1"

Rails

Generate the initializer in your Rails app:

rails generate open_sandbox:install

This creates config/initializers/open_sandbox.rb:

OpenSandbox.configure do |config|
  # config.base_url = ENV.fetch("SANDBOX_DOMAIN", "https://api.open-sandbox.ai")
  # config.api_key  = ENV.fetch("SANDBOX_API_KEY")
  # config.timeout  = 300
  # config.logger   = Rails.logger
end

Uncomment and adjust the options you need. By default the SDK reads SANDBOX_DOMAIN and SANDBOX_API_KEY from ENV, so the initializer can stay empty for most setups.

Quick Start

require "open_sandbox"

# Configure (picks up SANDBOX_DOMAIN / SANDBOX_API_KEY from ENV by default)
OpenSandbox.configure do |c|
  c.base_url = "http://localhost:8787"   # or "https://api.open-sandbox.ai"
  c.api_key  = "sk-..."                  # omit for local dev
  c.logger   = Logger.new($stdout, level: :debug)
end

Runner — one-shot code execution

# Python
result = OpenSandbox::Runner.python("print(2 ** 10)")
result.output    # => "1024\n"
result.success?  # => true
result.elapsed   # => 3.14

# Node.js
result = OpenSandbox::Runner.node("console.log('hi')")

# Shell
result = OpenSandbox::Runner.shell("echo $((6 * 7))")

# Custom image
result = OpenSandbox::Runner.call(
  image:   "ubuntu:24.04",
  command: ["bash", "-c", "ls /"],
  timeout: 60
)

The sandbox is always deleted after the run (even on error). Runner uses OpenSandbox.logger — set it to Rails.logger in your initializer to get structured logs.

Timeout resolution order: explicit timeout: argument → SANDBOX_TIMEOUT ENV → 300 s (default).

OpenSandbox client

client = OpenSandbox.client

# Create a sandbox
sandbox = client.sandboxes.create(
  image:           "python:3.11-slim",
  entrypoint:      ["python", "-c", "print(2 ** 10)"],
  timeout:         300,
  resource_limits: { "cpu" => "500m", "memory" => "256Mi" }
)
puts sandbox.id        # => "c9a03139-..."
puts sandbox.status.state  # => "Running"

# Wait until Running
client.sandboxes.wait_until(sandbox.id, target_state: OpenSandbox::SandboxState::RUNNING)

# Get endpoint for a service on port 8080
ep = client.sandboxes.endpoint(sandbox.id, port: 8080)
puts ep.url   # => "http://sb-xxx.sandbox.local:8080"

# Proxy an HTTP request directly to the sandbox
response = client.sandboxes.proxy(sandbox.id, port: 8080, method: :post,
                                  path: "/run", body: { code: "print(1)" })

# Fetch logs (Docker timestamps stripped automatically)
puts client.sandboxes.logs(sandbox.id, tail: 100)

# Lifecycle management
client.sandboxes.pause(sandbox.id)
client.sandboxes.resume(sandbox.id)
client.sandboxes.renew_expiration(sandbox.id, expires_at: Time.now + 3600)
client.sandboxes.delete(sandbox.id)

Resource Pools (pre-warm for low cold-start)

pool = client.pools.create(
  name:     "python-pool",
  template: { spec: { containers: [{ name: "main", image: "python:3.11-slim" }] } },
  capacity_spec: OpenSandbox::PoolCapacitySpec.new(
    buffer_max: 5, buffer_min: 2, pool_max: 20, pool_min: 2
  )
)

# Use the pool when creating a sandbox
client.sandboxes.create(
  image:      "python:3.11-slim",
  entrypoint: ["python", "app.py"],
  resource_limits: { "cpu" => "1", "memory" => "512Mi" },
  extensions: { "poolRef" => "python-pool" }
)

Diagnostics

puts client.sandboxes.logs(id, tail: 200, since: "10m")
puts client.sandboxes.events(id, limit: 50)
puts client.sandboxes.diagnostics(id)     # inspect + events + logs combined

Configuration

Option ENV var Default
base_url SANDBOX_DOMAIN http://localhost:8787
api_key SANDBOX_API_KEY nil (optional for local)
timeout SANDBOX_TIMEOUT 300 (seconds)
logger Logger.new(nil) (silent)

Error Handling

rescue OpenSandbox::NotFoundError   => e  # 404
rescue OpenSandbox::AuthenticationError   # 401
rescue OpenSandbox::ForbiddenError        # 403
rescue OpenSandbox::ConflictError         # 409
rescue OpenSandbox::ValidationError       # 422
rescue OpenSandbox::ServerError           # 5xx
rescue OpenSandbox::ConnectionError       # network / timeout
rescue OpenSandbox::Error                 # catch-all

Log Utility

Strip Docker-style timestamp prefixes from raw log strings:

raw = "2026-04-29T13:37:33.993Z 1024\n"
OpenSandbox::LogUtils.strip_timestamps(raw)
# => "1024\n"

Development

bundle install
bundle exec rake test     # run all tests

License

MIT