Skip to main content
This guide covers sandbox configuration options, including resources, mounted files, ports, annotations, secrets, and timeouts. Use it as a reference when you need to tailor a sandbox’s runtime environment to match the requirements of a workload, such as reserving GPU capacity, exposing a service, or injecting credentials. This page is for developers who use the cwsandbox Python SDK to launch and manage sandboxes.

Overview

You can set sandbox configuration in three places, listed from broadest to most specific scope:
  • SandboxDefaults: Shared defaults for all sandboxes in a session.
  • Sandbox.run() kwargs: Per-sandbox overrides.
  • @session.function() kwargs: Function-specific configuration.
from cwsandbox import ResourceOptions, Sandbox, SandboxDefaults, Session

# Via SandboxDefaults
defaults = SandboxDefaults(
    container_image="python:3.11",
    max_lifetime_seconds=3600,
    resources=ResourceOptions(
        requests={"cpu": "500m", "memory": "512Mi"},
        limits={"cpu": "2", "memory": "2Gi"},
    ),
)

# Via Sandbox.run() kwargs
sandbox = Sandbox.run(
    defaults=defaults,
    resources=ResourceOptions(
        requests={"cpu": "1", "memory": "1Gi"},
        limits={"cpu": "4", "memory": "4Gi"},
    ),
)

# Via @session.function() kwargs
with Session(defaults) as session:
    @session.function(resources={"cpu": "1", "memory": "1Gi"})
    def compute(x: int) -> int:
        return x * 2
The following sections describe each configuration area in detail.

Resources

Configure CPU, memory, and GPU resources using the ResourceOptions dataclass or a plain dict:
from cwsandbox import ResourceOptions, Sandbox

# Using ResourceOptions
sandbox = Sandbox.run(
    resources=ResourceOptions(
        requests={"cpu": "500m", "memory": "512Mi"},
        limits={"cpu": "2", "memory": "2Gi"},
    ),
)

# Using dict
sandbox = Sandbox.run(
    resources={
        "requests": {"cpu": "500m", "memory": "512Mi"},
        "limits": {"cpu": "2", "memory": "2Gi"},
    },
)
Both forms are equivalent: the SDK automatically converts dicts to ResourceOptions internally. ResourceOptions separates resource requests from limits. Requests tell the scheduler what the sandbox needs. Limits set the maximum it can use. For background on how Kubernetes uses requests and limits to assign Quality of Service classes, see the Kubernetes documentation.

ResourceOptions fields

FieldTypeDescription
requestsdict[str, str] | NoneCPU and memory requests for scheduling (for example, {"cpu": "500m", "memory": "512Mi"}).
limitsdict[str, str] | NoneCPU and memory limits the sandbox cannot exceed (for example, {"cpu": "2", "memory": "2Gi"}).
gpudict[str, Any] | NoneGPU configuration (for example, {"count": 1, "type": "H100"}).
All fields are optional and default to None, which uses backend defaults.

Guaranteed QoS

When requests equal limits, the sandbox receives a Guaranteed Quality of Service class. This reserves exact resources and prevents throttling. Use this for latency-sensitive workloads.
sandbox = Sandbox.run(
    resources=ResourceOptions(
        requests={"cpu": "2", "memory": "4Gi"},
        limits={"cpu": "2", "memory": "4Gi"},
    ),
)
For Guaranteed QoS, a flat dict shorthand is available. The SDK normalizes flat dicts by setting both requests and limits to the same values.
# Flat dict shorthand: requests and limits are set to identical values
sandbox = Sandbox.run(
    resources={"cpu": "2", "memory": "4Gi"},
)

Burstable QoS

When requests are lower than limits, the sandbox receives a Burstable Quality of Service class. Lower requests let the scheduler bin-pack more sandboxes, but each sandbox can burst up to its limit if capacity is available.
sandbox = Sandbox.run(
    resources=ResourceOptions(
        requests={"cpu": "500m", "memory": "512Mi"},
        limits={"cpu": "4", "memory": "4Gi"},
    ),
)

CPU values

Specify CPU in millicores or whole cores. For details, see Resource units in Kubernetes.
ValueMeaning
"100m"100 millicores (0.1 CPU)
"500m"500 millicores (0.5 CPU)
"1000m" or "1"1 full CPU core
"2000m" or "2"2 CPU cores

Memory values

Memory uses standard Kubernetes memory units:
ValueMeaning
"128Mi"128 mebibytes
"512Mi"512 mebibytes
"1Gi"1 gibibyte
"4Gi"4 gibibytes

GPU

Request GPU resources alongside CPU and memory.
sandbox = Sandbox.run(
    resources=ResourceOptions(
        requests={"cpu": "4", "memory": "16Gi"},
        limits={"cpu": "8", "memory": "32Gi"},
        gpu={"count": 1, "type": "H100"},
    ),
)
GPU configuration keys:
KeyTypeDescription
countintNumber of GPUs to request.
typestrGPU type (for example, "H100", "B200").
memory_gbintGPU memory in GB (optional).

Inspect confirmed resources

After a sandbox starts, inspect the confirmed resource allocation:
with Sandbox.run(
    resources=ResourceOptions(
        requests={"cpu": "500m", "memory": "512Mi"},
        limits={"cpu": "2", "memory": "2Gi"},
        gpu={"count": 1, "type": "H100"},
    ),
) as sb:
    print(sb.resource_requests)  # {"cpu": "500m", "memory": "512Mi"}
    print(sb.resource_limits)    # {"cpu": "2", "memory": "2Gi"}
    print(sb.resource_gpu)       # {"count": 1, "type": "H100"}

Configuration library interop

All configuration types (NetworkOptions, Secret, ResourceOptions) accept either the dataclass or a plain dict. This means ML configuration libraries that resolve configs to dicts or dict-like objects can pass values directly to the SDK without manual conversion. SandboxDefaults.from_dict() accepts a plain dict or an OmegaConf DictConfig and coerces nested fields automatically. Dicts become NetworkOptions, Secret, or ResourceOptions as needed, and lists become tuples.
# sandbox.yaml
container_image: "pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime"
max_lifetime_seconds: 3600
tags:
  - training
  - experiment-42
resources:
  requests:
    cpu: "1"
    memory: "2Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
  gpu:
    count: 1
    type: "H100"
network:
  egress_mode: "internet"
environment_variables:
  LOG_LEVEL: "info"
from omegaconf import OmegaConf
from cwsandbox import Sandbox, SandboxDefaults

cfg = OmegaConf.load("sandbox.yaml")
defaults = SandboxDefaults.from_dict(cfg)

with Sandbox.run(defaults=defaults) as sb:
    result = sb.exec(["python", "train.py"]).result()
Individual fields also accept dicts when you pass them directly to Sandbox.run() or session.sandbox(). The nested dict form for resources maps to ResourceOptions fields. The flat dict form ({"cpu": "1", "memory": "1Gi"}) is also accepted and treated as Guaranteed QoS.

Mounted files

Mounted files let you provide configuration files, scripts, or other read-only assets to the sandbox at startup, without baking them into a container image.
sandbox = Sandbox.run(
    mounted_files=[
        {
            "path": "/app/config.json",
            "content": '{"debug": true}',
        },
        {
            "path": "/app/script.py",
            "content": "print('hello')",
        },
    ],
)

# Files are available immediately
result = sandbox.exec(["python", "/app/script.py"]).result()

Mount options

FieldTypeDescription
pathstrAbsolute path in sandbox.
contentstrFile content (text).
Mounted files are read-only. Use write_file() for files that require modification.

Ports

Expose ports so processes inside the sandbox can serve traffic to outside clients:
sandbox = Sandbox.run(
    "python", "-m", "http.server", "8080",
    ports=[
        {"container_port": 8080},
    ],
)

Port configuration

FieldTypeDescription
container_portintPort inside the sandbox.

Network

Configure network options using the NetworkOptions dataclass or a plain dict:
from cwsandbox import NetworkOptions, Sandbox

# Using NetworkOptions
sandbox = Sandbox.run(
    network=NetworkOptions(
        ingress_mode="public",
        exposed_ports=(8080,),
    ),
)

# Using dict
sandbox = Sandbox.run(
    network={"ingress_mode": "public", "exposed_ports": [8080]},
)
Both forms are equivalent: the SDK automatically converts dicts to NetworkOptions internally.

NetworkOptions fields

FieldTypeDescription
ingress_modestr | NoneControls inbound traffic, such as "public" (internet accessible) or "internal" (cluster only).
exposed_portstuple[int, ...] | NonePorts to expose (required with ingress_mode). Pass as tuple (8080,) or list [8080].
egress_modestr | NoneControls outbound traffic, such as "internet" (full access), "isolated" (no external), or "org" (org-internal only).
All fields are optional and default to None, which uses backend defaults.

Set network in SandboxDefaults

Set a default network configuration for all sandboxes:
from cwsandbox import NetworkOptions, SandboxDefaults, Session

defaults = SandboxDefaults(
    network=NetworkOptions(egress_mode="internet"),
)

with Session(defaults) as session:
    # All sandboxes inherit the network config
    sb1 = session.sandbox()  # Uses egress_mode="internet"
    sb2 = session.sandbox()  # Uses egress_mode="internet"

    # Override for specific sandbox
    sb3 = session.sandbox(network=NetworkOptions(egress_mode="user"))

Annotations

Add Kubernetes pod annotations to sandboxes. Annotations are key-value string pairs attached to the underlying pod, useful for integrations that read pod metadata, such as schedulers, cost-allocation tools, or external automation.
sandbox = Sandbox.run(
    annotations={
        "team": "ml-infra",
        "experiment": "training-run-42",
    },
)

Session-level defaults

Set default annotations for all sandboxes in a session:
defaults = SandboxDefaults(
    annotations={
        "team": "ml-infra",
        "managed-by": "sandbox-sdk",
    },
)

with Session(defaults) as session:
    # Inherits default annotations
    sb1 = session.sandbox()

    # Merge: explicit annotations override defaults on key collision
    sb2 = session.sandbox(
        annotations={"experiment": "run-42", "team": "ml-research"},
    )
    # sb2 receives: team=ml-research, managed-by=sandbox-sdk, experiment=run-42

Merge behavior

When you provide both SandboxDefaults.annotations and per-sandbox annotations, the SDK merges them. Explicit per-sandbox values win on key collision, matching the same semantics as environment_variables.

SUNK integration

To pass Slurm context as pod annotations for SUNK integration, see the SUNK Pod Scheduler integration guide.

Validation

The SDK does not validate annotation keys or values. The server handles validation of reserved key prefixes, value format, and maximum entry count.

Secrets

Use secrets to provide credentials such as API tokens or database passwords to a sandbox without exposing the values in your client-side code. Inject secrets from secret stores as environment variables using the Secret type:
from cwsandbox import Sandbox, Secret

with Sandbox.run(
    "pip", "install", "huggingface_hub",
    secrets=[
        Secret(store="wandb", name="HF_TOKEN"),
    ],
) as sandbox:
    sandbox.wait()
    result = sandbox.exec([
        "python", "-c",
        "from huggingface_hub import whoami; print(whoami()['name'])",
    ]).result()
    print(result.stdout.strip())  # Your Hugging Face username
Unlike environment_variables, secrets are never passed in plaintext. The server resolves them from the named store and injects them securely.
Administrators configure secret stores at the organization level. Stores connect to external providers (for example, W&B Secret Manager) and resolve server-side at sandbox creation. Stores are independent of client-side authentication.

Secret fields

FieldTypeDefaultDescription
storestrrequiredSecret store configured for your organization (for example, "wandb").
namestrrequiredName of the secret in the store.
fieldstr""Specific field within a structured secret.
env_varstrsame as nameEnvironment variable that receives the injected secret.

Common patterns

from cwsandbox import Secret

# Minimal: env_var defaults to name
Secret(store="wandb", name="HF_TOKEN")
# -> injected as HF_TOKEN

# Custom env_var name
Secret(store="wandb", name="HF_TOKEN", env_var="HUGGINGFACE_TOKEN")
# -> injected as HUGGINGFACE_TOKEN

# Extract a field from a structured secret
Secret(store="wandb", name="db-credentials", field="password", env_var="DB_PASS")
# -> injected as DB_PASS

Set secrets in SandboxDefaults

Share secrets across all sandboxes in a session:
from cwsandbox import SandboxDefaults, Secret, Session

defaults = SandboxDefaults(
    secrets=(
        Secret(store="wandb", name="HF_TOKEN"),
        Secret(store="wandb", name="OPENAI_API_KEY"),
    ),
)

with Session(defaults) as session:
    # All sandboxes receive both secrets
    sb1 = session.sandbox()
    sb2 = session.sandbox()

    # Add more secrets for a specific sandbox
    sb3 = session.sandbox(
        secrets=[Secret(store="vault", name="DB_PASS")],
    )
    # sb3 receives all three secrets (defaults + explicit merged)
If two secrets in the effective list (defaults merged with per-sandbox secrets) target the same resolved env_var with different store, name, or field, the SDK raises a ValueError whose message starts with Conflicting secrets for env_var. This check runs locally while the SDK constructs the Sandbox object, before the SDK sends any request to start the sandbox. The SDK ignores exact duplicates when the resolved env_var, store, name, and field all match.

Timeouts

Two distinct timeout settings control sandbox behavior: a per-command client-side timeout and a server-side maximum sandbox lifetime.

timeout_seconds

Per-command timeout:
# Per-exec timeout
result = sandbox.exec(
    ["python", "long_script.py"],
    timeout_seconds=300,  # 5 minute timeout
).result()
This controls how long the client waits for a response. If the client exceeds the timeout, it raises SandboxTimeoutError.

max_lifetime_seconds

Maximum sandbox lifetime (set in SandboxDefaults):
defaults = SandboxDefaults(
    container_image="python:3.11",
    max_lifetime_seconds=3600,  # 1 hour max lifetime
)

with Sandbox.run(defaults=defaults) as sandbox:
    # Sandbox automatically terminates after 1 hour
    pass
This is a server-side limit. The server terminates the sandbox when it reaches this age, regardless of activity.

Complete example

The following example combines defaults, per-sandbox overrides, mounted files, ports, and secrets into a single configuration that mirrors how a production workload might be launched:
from cwsandbox import NetworkOptions, ResourceOptions, Sandbox, SandboxDefaults, Secret

defaults = SandboxDefaults(
    container_image="python:3.11",
    max_lifetime_seconds=1800,  # 30 minutes
    tags=("production", "ml-pipeline"),
    resources=ResourceOptions(
        requests={"cpu": "1", "memory": "2Gi"},
        limits={"cpu": "4", "memory": "8Gi"},
    ),
    network=NetworkOptions(egress_mode="internet"),
    secrets=(Secret(store="wandb", name="HF_TOKEN"),),
)

with Sandbox.run(
    defaults=defaults,
    mounted_files=[
        {
            "path": "/app/config.yaml",
            "content": "model: gpt-4\nmax_tokens: 1000",
        },
    ],
    ports=[
        {"container_port": 8000},
    ],
) as sandbox:
    # Install dependencies
    sandbox.exec(["pip", "install", "fastapi", "uvicorn"]).result()

    # Run application
    result = sandbox.exec(
        ["python", "-c", "print('Server started')"],
        timeout_seconds=60,
    ).result()
    print(result.stdout)
Last modified on May 29, 2026