FastAPI control plane for OpenSandbox that manages sandbox lifecycle on Docker (ready) and Kubernetes (planned) runtimes.
Reason this release was yanked:
Incompatible
Project description
OpenSandbox Server
English | 中文
A production-grade, FastAPI-based service for managing the lifecycle of containerized sandboxes. It acts as the control plane to create, run, monitor, and dispose isolated execution environments across container platforms.
Features
Core capabilities
- Lifecycle APIs: Standardized REST interfaces for create, start, pause, resume, delete
- Pluggable runtimes:
- Docker: Production-ready
- Kubernetes: Production-ready (see
kubernetes/for deployment)
- Lifecycle cleanup modes: Configurable TTL with renewal, or manual cleanup with explicit delete
- Access control: API Key authentication (
OPEN-SANDBOX-API-KEY); can be disabled for local/dev - Networking modes:
- Host: shared host network, performance first
- Bridge: isolated network with built-in HTTP routing
- Resource quotas: CPU/memory limits with Kubernetes-style specs
- Observability: Unified status with transition tracking
- Registry support: Public and private images
Extended capabilities
- Async provisioning: Background creation to reduce latency
- Timer restoration: Expiration timers restored after restart
- Env/metadata injection: Per-sandbox environment and metadata
- Port resolution: Dynamic endpoint generation
- Structured errors: Standard error codes and messages
Metadata keys under the reserved prefix opensandbox.io/ are system-managed
and cannot be supplied by users.
Requirements
- Python: 3.10 or higher
- Package Manager: uv (recommended) or pip
- Runtime Backend:
- Docker Engine 20.10+ (for Docker runtime)
- Kubernetes 1.21.1+ (for Kubernetes runtime)
- Operating System: Linux, macOS, or Windows with WSL2
Quick Start
Installation
- Install from PyPI:
For source development or contributions, you can still clone the repo and run
uv syncinsideserver/.uv pip install opensandbox-server
Configuration
The server uses a TOML configuration file to select and configure the underlying runtime.
Init configuration from simple example:
# run opensandbox-server -h for help
opensandbox-server init-config ~/.sandbox.toml --example docker
Create K8S configuration file
The K8S version of the Sandbox Operator needs to be deployed in the cluster, refer to the Kubernetes directory.
# run opensandbox-server -h for help
opensandbox-server init-config ~/.sandbox.toml --example k8s
[optional] Edit configuration for your environment
- For quick e2e/demo (specify which one):
opensandbox-server init-config ~/.sandbox.toml --example docker # or docker-zh|k8s|k8s-zh # add --force to overwrite existing file
- Render the full schema-driven skeleton (no defaults, just placeholders) by omitting --example:
opensandbox-server init-config ~/.sandbox.toml # add --force to overwrite existing file
[optional] Edit ~/.sandbox.toml for your environment
Before you start the server, edit the configuration file to suit your environment. You could also generate a new empty configuration file by opensandbox-server init-config ~/.sandbox.toml.
Docker runtime + host networking
[server]
host = "0.0.0.0"
port = 8080
log_level = "INFO"
api_key = "your-secret-api-key-change-this"
max_sandbox_timeout_seconds = 86400 # Maximum TTL for requests that specify timeout
[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.0.7"
[docker]
network_mode = "host" # Containers share host network; only one sandbox instance at a time
Docker runtime + bridge networking
[server]
host = "0.0.0.0"
port = 8080
log_level = "INFO"
api_key = "your-secret-api-key-change-this"
max_sandbox_timeout_seconds = 86400 # Maximum TTL for requests that specify timeout
[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.0.7"
[docker]
network_mode = "bridge" # Isolated container networking
Docker Compose deployment (server runs in a container)
When opensandbox-server itself runs inside Docker Compose and manages sandboxes via
mounted /var/run/docker.sock, configure a reachable host value for bridge-mode endpoint
resolution:
[docker]
network_mode = "bridge"
host_ip = "host.docker.internal" # or host LAN IP (for Linux: explicit host IP is recommended)
Why this matters:
- In bridge mode, sandbox containers get internal Docker IPs.
- External callers usually cannot reach those internal IPs directly.
host_iplets endpoint resolution return host-reachable addresses.
For SDK/API clients that cannot directly reach sandbox bridge addresses, request proxied endpoints through the server:
curl -H "OPEN-SANDBOX-API-KEY: your-secret-api-key" \
"http://localhost:8080/v1/sandboxes/<sandbox-id>/endpoints/44772?use_server_proxy=true"
The returned endpoint is rewritten to the server proxy route:
<server-host>/sandboxes/<sandbox-id>/proxy/<port>
Reference runtime compose file:
server/docker-compose.example.yaml
Sandbox TTL configuration
timeoutrequests must be at least 60 seconds.- The maximum allowed TTL is controlled by
server.max_sandbox_timeout_seconds. - Omit
timeoutor set it tonullin the create request to use manual cleanup mode instead of automatic expiration.
Upgrade order for manual cleanup
- Existing TTL-only clients can continue to work without changes as long as they do not encounter manual-cleanup sandboxes.
- Manual cleanup changes the lifecycle response contract:
expiresAtmay benull, and other nullable lifecycle fields may also be serialized explicitly asnull. - In practice this can include fields such as
metadata,status.reason,status.message, andstatus.lastTransitionAt, depending on the sandbox state and the server response model. - Before creating any manual-cleanup sandbox, upgrade every SDK/client that may call
create,get, orliston the lifecycle API. - Recommended rollout order:
- Upgrade SDKs/clients
- Upgrade the server
- Start creating sandboxes with
timeoutomitted ornull
- Do not introduce manual-cleanup sandboxes into a shared environment while old SDKs are still actively reading lifecycle responses.
Security hardening (applies to all Docker modes)
[docker]
# Drop dangerous capabilities and block privilege escalation by default
drop_capabilities = ["AUDIT_WRITE", "MKNOD", "NET_ADMIN", "NET_RAW", "SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "SYS_TIME", "SYS_TTY_CONFIG"]
no_new_privileges = true
apparmor_profile = "" # e.g. "docker-default" when AppArmor is available
# Limit fork bombs and optionally enforce seccomp / read-only rootfs
pids_limit = 512 # set to null to disable
seccomp_profile = "" # path or profile name; empty uses Docker default
Further reading on Docker container security: https://docs.docker.com/engine/security/
For common issues and solutions, see Troubleshooting.
Secure container runtime (optional)
OpenSandbox supports secure container runtimes for enhanced isolation:
[secure_runtime]
type = "gvisor" # Options: "", "gvisor", "kata", "firecracker"
docker_runtime = "runsc" # Docker OCI runtime name (for gVisor, Kata)
# k8s_runtime_class = "gvisor" # Kubernetes RuntimeClass name (for K8s)
type=""(default): No secure runtime, uses runctype="gvisor": Uses gVisor (runsc) for user-space kernel isolationtype="kata": Uses Kata Containers for VM-level isolationtype="firecracker": Uses Firecracker microVM (Kubernetes only)
Detailed guide: See Secure Container Runtime Guide for complete installation instructions, system requirements, and troubleshooting.
Docker daemon setup for gVisor:
{
"runtimes": {
"runsc": {
"path": "/usr/bin/runsc"
}
}
}
Kubernetes setup: Create RuntimeClass before using:
kubectl create -f - <<EOF
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
EOF
Ingress exposure (direct | gateway)
[ingress]
mode = "direct" # docker runtime only supports direct
# gateway.address = "*.example.com" # host only (domain or IP[:port]); scheme is not allowed
# gateway.route.mode = "wildcard" # wildcard | uri | header
mode=direct: default; required whenruntime.type=docker(client ↔ sandbox direct reachability, no L7 gateway).mode=gateway: configure external ingress.gateway.address: wildcard domain required whengateway.route.mode=wildcard; otherwise must be domain, IP, or IP:port. Do not include scheme; clients decide http/https.gateway.route.mode:wildcard(host-based wildcard),uri(path-prefix),header(header-based routing).- Response format examples:
wildcard:<sandbox-id>-<port>.example.com/path/to/requesturi:10.0.0.1:8000/<sandbox-id>/<port>/path/to/requestheader:gateway.example.comwith headerOpenSandbox-Ingress-To: <sandbox-id>-<port>
Kubernetes runtime
[runtime]
type = "kubernetes"
execd_image = "opensandbox/execd:v1.0.7"
[kubernetes]
kubeconfig_path = "~/.kube/config"
namespace = "opensandbox"
workload_provider = "batchsandbox" # or "agent-sandbox"
informer_enabled = true # Beta: enable watch-based cache
informer_resync_seconds = 300 # Beta: full list interval
informer_watch_timeout_seconds = 60 # Beta: watch restart interval
- Informer settings are beta and enabled by default to reduce API calls; set
informer_enabled = falseto turn off. - Resync and watch timeouts control how often the cache refreshes; tune for your cluster API limits.
Egress configuration
The [egress] block configures the egress sidecar image and enforcement mode. The server only starts this sidecar when a sandbox is created with a networkPolicy (outbound allow/deny rules). If the create request omits networkPolicy, no egress sidecar is added and outbound traffic is not restricted by this mechanism.
Keys
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
image |
string | — | Yes whenever networkPolicy is used in a create request |
OCI image containing the egress binary. Pulled before the sidecar starts. |
mode |
dns or dns+nft |
dns |
No | How the sidecar enforces policy. Written to the sidecar as OPENSANDBOX_EGRESS_MODE (see below). |
mode values
dns: DNS-based enforcement via the in-sidecar DNS proxy. No nftables layer-2 rules from this path. CIDR and static IP targets in the policy are not enforced (use domain-style rules only if you rely ondnsmode).dns+nft: Same DNS path, plus nftables where available (see the egress component README for capabilities and fallbacks). CIDR and static IP allow/deny rules are supported via nftables when the table is applied successfully.
Per-request networkPolicy
- Rules are defined on
CreateSandboxRequest.networkPolicy(default action and ordered egress rules: hostnames / patterns, and IP or CIDR entries when usingdns+nft). - The serialized policy is passed into the sidecar as
OPENSANDBOX_EGRESS_RULES(JSON). - An auth token may be attached for the egress HTTP API; see runtime behavior below.
Docker runtime
egress.imagemust be set in config when clients sendnetworkPolicy; otherwise the request is rejected.- Outbound policy requires
docker.network_mode = "bridge". Requests withnetworkPolicyare rejected fornetwork_mode=hostor for user-defined Docker networks that are incompatible with the sidecar attachment model. - The main sandbox container shares the sidecar’s network namespace, drops
NET_ADMIN, and relies on the sidecar for policy; the sidecar keepsNET_ADMIN. - IPv6 is disabled in the shared namespace so allow/deny behavior stays consistent.
Kubernetes runtime
- When
networkPolicyis present, the workload pod includes an egress sidecar built fromegress.image, in addition to the main sandbox container. egress.imageis required in the same way as for Docker.
Operational notes
- The sidecar image is pulled (or validated) before start; delete, expiry, and failure paths attempt to remove the sidecar.
- For deeper behavior (DNS proxy, nftables, limits), refer to the egress component documentation under
components/egress/.
Example (~/.sandbox.toml)
[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.0.7"
[egress]
image = "opensandbox/egress:v1.0.3"
mode = "dns"
Example create request with networkPolicy
{
"image": {"uri": "python:3.11-slim"},
"entrypoint": ["python", "-m", "http.server", "8000"],
"timeout": 3600,
"resourceLimits": {"cpu": "500m", "memory": "512Mi"},
"networkPolicy": {
"defaultAction": "deny",
"egress": [
{"action": "allow", "target": "pypi.org"},
{"action": "allow", "target": "*.python.org"}
]
}
}
Run the server
Start the server using the installed CLI (reads ~/.sandbox.toml by default):
opensandbox-server
The server will start at http://0.0.0.0:8080 (or your configured host/port).
Run the server (installed package)
After installing the package (wheel or PyPI), you can use the CLI entrypoint:
opensandbox-server --config ~/.sandbox.toml
Health check
curl http://localhost:8080/health
Expected response:
{"status": "healthy"}
API documentation
Once the server is running, interactive API documentation is available:
- Swagger UI: http://localhost:8080/docs
- ReDoc: http://localhost:8080/redoc
Further reading on Docker container security: https://docs.docker.com/engine/security/
API authentication
Authentication is enforced only when server.api_key is set. If the value is empty or missing, the middleware skips API Key checks (intended for local/dev). For production, always set a non-empty server.api_key and send it via the OPEN-SANDBOX-API-KEY header.
All API endpoints (except /health, /docs, /redoc) require authentication via the OPEN-SANDBOX-API-KEY header when authentication is enabled:
curl http://localhost:8080/v1/sandboxes
Example usage
Create a Sandbox
curl -X POST "http://localhost:8080/v1/sandboxes" \
-H "OPEN-SANDBOX-API-KEY: your-secret-api-key" \
-H "Content-Type: application/json" \
-d '{
"image": {
"uri": "python:3.11-slim"
},
"entrypoint": [
"python",
"-m",
"http.server",
"8000"
],
"timeout": 3600,
"resourceLimits": {
"cpu": "500m",
"memory": "512Mi"
},
"env": {
"PYTHONUNBUFFERED": "1"
},
"metadata": {
"team": "backend",
"project": "api-testing"
}
}'
Response:
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": {
"state": "Pending",
"reason": "CONTAINER_STARTING",
"message": "Sandbox container is starting.",
"lastTransitionAt": "2024-01-15T10:30:00Z"
},
"metadata": {
"team": "backend",
"project": "api-testing"
},
"expiresAt": "2024-01-15T11:30:00Z",
"createdAt": "2024-01-15T10:30:00Z",
"entrypoint": ["python", "-m", "http.server", "8000"]
}
Get Sandbox Details
curl -H "OPEN-SANDBOX-API-KEY: your-secret-api-key" \
http://localhost:8080/v1/sandboxes/a1b2c3d4-5678-90ab-cdef-1234567890ab
Get Service Endpoint
curl -H "OPEN-SANDBOX-API-KEY: your-secret-api-key" \
http://localhost:8080/v1/sandboxes/a1b2c3d4-5678-90ab-cdef-1234567890ab/endpoints/8000
# execd (agent) endpoint
curl -H "OPEN-SANDBOX-API-KEY: your-secret-api-key" \
http://localhost:8080/v1/sandboxes/a1b2c3d4-5678-90ab-cdef-1234567890ab/endpoints/44772
Response:
{
"endpoint": "sandbox.example.com/a1b2c3d4-5678-90ab-cdef-1234567890ab/8000"
}
Renew Expiration
curl -X POST "http://localhost:8080/v1/sandboxes/a1b2c3d4-5678-90ab-cdef-1234567890ab/renew-expiration" \
-H "OPEN-SANDBOX-API-KEY: your-secret-api-key" \
-H "Content-Type: application/json" \
-d '{
"expiresAt": "2024-01-15T12:30:00Z"
}'
Delete a Sandbox
curl -X DELETE \
-H "OPEN-SANDBOX-API-KEY: your-secret-api-key" \
http://localhost:8080/v1/sandboxes/a1b2c3d4-5678-90ab-cdef-1234567890ab
Architecture
Component responsibilities
- API Layer (
src/api/): HTTP request handling, validation, and response formatting - Service Layer (
src/services/): Business logic for sandbox lifecycle operations - Middleware (
src/middleware/): Cross-cutting concerns (authentication, logging) - Configuration (
src/config.py): Centralized configuration management - Runtime Implementations: Platform-specific sandbox orchestration
Sandbox lifecycle states
create()
│
▼
┌─────────┐
│ Pending │────────────────────┐
└────┬────┘ │
│ │
│ (provisioning) │
▼ │
┌─────────┐ pause() │
│ Running │───────────────┐ │
└────┬────┘ │ │
│ resume() │ │
│ ┌────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────┐ │
├─│ Paused │ │
│ └────────┘ │
│ │
│ delete() or expire() │
▼ │
┌──────────┐ │
│ Stopping │ │
└────┬─────┘ │
│ │
├────────────────┬────────┘
│ │
▼ ▼
┌────────────┐ ┌────────┐
│ Terminated │ │ Failed │
└────────────┘ └────────┘
Configuration reference
Server configuration
| Key | Type | Default | Description |
|---|---|---|---|
server.host |
string | "0.0.0.0" |
Interface to bind |
server.port |
integer | 8080 |
Port to listen on |
server.log_level |
string | "INFO" |
Python logging level |
server.api_key |
string | null |
API key for authentication |
server.eip |
string | null |
Bound public IP; when set, used as the host part when returning sandbox endpoints (Docker runtime) |
Runtime configuration
| Key | Type | Required | Description |
|---|---|---|---|
runtime.type |
string | Yes | Runtime implementation ("docker" or "kubernetes") |
runtime.execd_image |
string | Yes | Container image with execd binary |
Egress configuration
| Key | Type | Default | Required if using networkPolicy |
Description |
|---|---|---|---|---|
egress.image |
string | — | Yes | Egress sidecar image (OCI reference). |
egress.mode |
dns | dns+nft |
dns |
No | OPENSANDBOX_EGRESS_MODE. CIDR/IP rules need dns+nft; dns is domain-oriented only. |
Docker configuration
| Key | Type | Default | Description |
|---|---|---|---|
docker.network_mode |
string | "host" |
Network mode ("host" or "bridge") |
Agent-sandbox configuration
| Key | Type | Default | Description |
|---|---|---|---|
agent_sandbox.template_file |
string | null |
Sandbox CR YAML template for agent-sandbox (used when kubernetes.workload_provider = "agent-sandbox") |
agent_sandbox.shutdown_policy |
string | "Delete" |
Shutdown policy on expiry ("Delete" or "Retain") |
agent_sandbox.ingress_enabled |
boolean | true |
Whether ingress routing is expected to be enabled |
Environment variables
| Variable | Description |
|---|---|
SANDBOX_CONFIG_PATH |
Override config file location |
DOCKER_HOST |
Docker daemon URL (e.g., unix:///var/run/docker.sock) |
PENDING_FAILURE_TTL |
TTL for failed pending sandboxes in seconds (default: 3600) |
Development
Code quality
Run linter:
uv run ruff check
Auto-fix issues:
uv run ruff check --fix
Format code:
uv run ruff format
Testing
Run all tests:
uv run pytest
Run with coverage:
uv run pytest --cov=src --cov-report=html
Run specific test:
uv run pytest tests/test_docker_service.py::test_create_sandbox_requires_entrypoint
License
This project is licensed under the terms specified in the LICENSE file in the repository root.
Contributing
Contributions are welcome. Suggested flow:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for new functionality
- Ensure all tests pass (
uv run pytest) - Run linting (
uv run ruff check) - Commit with clear messages
- Push to your fork
- Open a Pull Request
Support
- Documentation: See
DEVELOPMENT.mdfor development guidance - Issues: Report defects via GitHub Issues
- Discussions: Use GitHub Discussions for Q&A and ideas
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file opensandbox_server-0.1.8.tar.gz.
File metadata
- Download URL: opensandbox_server-0.1.8.tar.gz
- Upload date:
- Size: 95.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c907a936bc1b1d73e4ce3e6bb303a6a256edacf07c5930042236cba2bd8017b6
|
|
| MD5 |
3a0ca047fed90e6feff777845ee677f0
|
|
| BLAKE2b-256 |
34d8252143b4256e95014e87ece0f9bd326021a9cc0bcfde5a0e0c3c42a42bb8
|
File details
Details for the file opensandbox_server-0.1.8-py3-none-any.whl.
File metadata
- Download URL: opensandbox_server-0.1.8-py3-none-any.whl
- Upload date:
- Size: 127.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.12 {"installer":{"name":"uv","version":"0.10.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8698c32867e45ff47ac0c4e68b4201e735cdf849892014e90a3d73d09740d948
|
|
| MD5 |
11b96b96c6ad333216417906a6a74e1d
|
|
| BLAKE2b-256 |
c03b460c4871f72e66dde840df061bb816c7a6f699bd975785b2d091a9046986
|