Lightweight Linux namespace sandbox with persistent shell and instant reset
Project description
nitrobox
Lightweight Linux namespace sandbox with persistent shell and instant filesystem reset.
50x faster lifecycle than Docker. Designed for high-frequency workloads like RL training where environments are created, reset, and destroyed thousands of times.
Drop-in Docker replacement
Real-world example: SWE-bench evaluation runs 2,294 task instances, each creating a Docker container, applying a patch, running tests, and destroying it. Here's how to migrate:
| SWE-bench (Docker SDK) | nitrobox |
|---|---|
import docker
client = docker.from_env()
for task in swebench_tasks:
# Create — ~320ms
c = client.containers.run(
task.instance_image,
command="tail -f /dev/null",
detach=True,
)
# Eval — ~17ms/cmd
c.exec_run("bash /eval.sh")
# "Reset" = destroy + recreate — ~820ms
c.stop(); c.remove()
|
from nitrobox import Sandbox, SandboxConfig
for task in swebench_tasks:
# Create — ~25ms (13x faster)
sb = Sandbox(SandboxConfig.from_docker(
task.instance_image,
))
# Eval — ~11ms/cmd (1.6x faster)
sb.run("bash /eval.sh")
# Reset — ~16ms (38x faster, no recreate)
sb.reset()
|
Same images, same parameters — no root required. Also supports from_docker_run() for CLI commands and ComposeProject for docker-compose.yml.
Reproduce: python examples/bench_swebench.py (numbers above measured on Ryzen 9800X3D; results vary by CPU)
Key features
- No root required: Full isolation via user namespaces (overlayfs, PID/UTS/IPC/net namespace, chroot)
- Persistent shell: ~11ms per command (vs ~17ms Docker exec)
- Instant reset: O(1) overlayfs upper rename — ~7ms including shell restart
- Fast lifecycle: ~7ms create, ~2ms delete
- Process checkpoint/restore: Full process-state save/restore (memory, registers, fds) for RL partial rollout
- Port mapping: Vendored
pastabinary for NAT + TCP port forwarding, zero dependencies - Security hardening: seccomp-bpf, Landlock, masked/readonly paths, capability drop — all on by default
- OCI ENTRYPOINT: Auto-runs image entrypoint scripts (e.g. database init). ComposeProject combines entrypoint+CMD as background process matching Docker semantics
- cgroup v2: CPU, memory, PID, IO limits with PSI pressure monitoring
- Zero-copy image layers: Uses BuildKit for image builds (in-memory cache, true concurrent builds) and containers/storage for layer access — no Docker daemon needed
- Docker Compose compatibility: Parse
docker-compose.yml, per-network isolation via shared namespaces, Docker-matching health check daemon (interval,start_period,start_interval,retries) - CLI:
nitrobox ps/kill/cleanup/setup/buildkit-stopfor sandbox and daemon management
Requirements
- Linux kernel 5.11+
util-linux(unshare)- Python >= 3.12
- uidmap (
sudo apt-get install -y uidmap)
No Docker or Podman required. Images are pulled directly from container registries via built-in OCI client. If Docker/Podman is available, it's used for faster local cache hits.
The pip package bundles static binaries for pasta (port mapping) and criu (process checkpointing) — no extra install needed.
Ubuntu 24.04 / 23.10+ (AppArmor)
Ubuntu defaults to blocking unprivileged user namespaces. Run once to enable:
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
# Persist across reboots:
echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/99-userns.conf
Install
pip install nitrobox
nitrobox setup
Development build
Requires Rust, Go 1.25+, and maturin:
uv sync --all-extras --dev
nitrobox setup
Quick start
from nitrobox import Sandbox, SandboxConfig
config = SandboxConfig(
image="ubuntu:22.04",
working_dir="/workspace",
)
with Sandbox(config, name="worker-0") as sb:
output, ec = sb.run("echo hello world")
print(output) # "hello world\n"
sb.write_file("/workspace/payload.py", "print('hello')")
content = sb.read_file("/workspace/payload.py")
sb.reset() # instant filesystem reset
# auto cleanup on exit
No sudo required. The sandbox automatically uses user namespaces for full isolation. OCI image config (WORKDIR, ENV, ENTRYPOINT) is auto-applied — user values take precedence.
Configuration
SandboxConfig(
image="ubuntu:22.04", # Docker image or rootfs path
working_dir="/workspace", # Initial cwd inside sandbox
environment={"FOO": "bar"}, # Extra env vars
hostname="worker-0", # Custom hostname (UTS namespace)
dns=["8.8.8.8", "1.1.1.1"], # Custom DNS servers
read_only=True, # Read-only rootfs
# Volumes
volumes=[
"/host/data:/data:ro", # read-only bind mount
"/host/project:/workspace:rw", # read-write bind mount
"/host/project:/workspace:cow", # copy-on-write (overlayfs)
],
# Resource limits (cgroup v2)
cpu_max="0.5", # 50% of one CPU (also: "2" for 2 cores, "50%")
memory_max="512m", # 512MB (also accepts "2g", "536870912")
memory_swap="1g", # total memory+swap (Docker semantics)
pids_max="256",
cpu_shares=1024, # relative CPU weight (Docker --cpu-shares)
io_max="/dev/sda 10mb", # 10MB/s write limit (also: "rbps=5mb wbps=10mb")
cpuset_cpus="0-3", # Pin to CPU 0-3
oom_score_adj=500, # Prefer killing sandbox over host
shm_size="256m", # /dev/shm size (default 256m)
tmpfs=["/run:size=100m"], # additional tmpfs mounts
# Networking
net_isolate=True, # Loopback only (or use port_map for NAT)
port_map=["8080:80", "3000:3000"], # host:container TCP ports
# Security
seccomp=True, # seccomp-bpf (default: True)
writable_paths=["/workspace"], # Landlock: only these paths writable (None=no restriction)
readable_paths=["/usr", "/lib"],# Landlock: only these paths readable (None=no restriction)
allowed_ports=[80, 443], # Landlock: only these TCP ports connectable (None=no restriction)
# Devices (rootless: user must have group access, e.g. kvm group)
devices=["/dev/nvidia0", "/dev/nvidiactl"],
# Capabilities
cap_add=["NET_RAW", "NET_ADMIN"], # Extra capabilities to keep (applied at runtime via Rust init chain)
# OCI entrypoint (auto-filled from image config if not set)
# Direct Sandbox API: wraps the shell; ComposeProject: runs as background with CMD
entrypoint=["/docker-entrypoint.sh"],
)
API
| Method | Description |
|---|---|
sb.run(cmd, timeout=None) |
Run command, returns (output, exit_code) |
sb.reset() |
Reset filesystem to initial state |
sb.delete() |
Full cleanup (unmount, remove cgroup, delete files) |
sb.copy_to(local, container) |
Copy file into sandbox |
sb.copy_from(container, local) |
Copy file out of sandbox |
sb.read_file(path) |
Read file content |
sb.write_file(path, content) |
Write file content |
sb.popen(cmd) |
Start interactive process (stdin/stdout/stderr pipes) |
sb.run_background(cmd) |
Start background process, returns handle |
sb.check_background(handle) |
Check output and status |
sb.list_background() |
List all background processes |
sb.stop_background(handle) |
Stop a background process |
sb.snapshot("tag") |
Save filesystem state (tag optional, auto-ID if omitted) |
sb.restore("tag") |
Restore to a snapshot (omit for latest) |
sb.list_snapshots() |
List available snapshot tags/IDs |
sb.delete_snapshot("tag") |
Delete a snapshot |
sb.save_as_image(name) |
Export sandbox as Docker image |
sb.pressure() |
cgroup v2 PSI (cpu/memory/io) |
sb.reclaim_memory() |
Hint kernel to swap out idle pages |
sb.features |
Dict of active kernel features |
sb.rootfs |
Host path to sandbox rootfs |
await sb.arun(cmd) |
Async version of run() |
await sb.areset() |
Async version of reset() |
await sb.adelete() |
Async version of delete() |
await sb.asnapshot() |
Async version of snapshot() |
await sb.arestore() |
Async version of restore() |
Snapshots (RL step-wise rollback)
Save and restore filesystem state at any point. Useful for tree search, partial rollback, and best-of-N exploration:
# Named snapshots (like Docker tags)
sb.snapshot("before_test")
sb.run("risky change")
sb.restore("before_test") # rollback by name
# Auto-ID snapshots
sb.snapshot() # → 0
sb.snapshot() # → 1
sb.restore() # restore to latest (1)
sb.restore(0) # restore to specific ID
# Tree search
branch = sb.snapshot("branch_point")
sb.run("action_a") # try A
sb.restore("branch_point") # rollback
sb.run("action_b") # try B
Async API
All core methods have async variants (arun, areset, adelete, asnapshot, arestore) for use in async frameworks (Ray, asyncio-based RL loops):
async def rollout(i):
sb = Sandbox(SandboxConfig(image="ubuntu:22.04"), name=f"worker-{i}")
output, ec = await sb.arun("python solve.py")
await sb.areset()
await sb.adelete()
await asyncio.gather(*(rollout(i) for i in range(100)))
Process checkpointing (CRIU)
Full process-state checkpoint/restore — memory, registers, fds, cwd. Requires root; CRIU binary is bundled.
from nitrobox import CheckpointManager
mgr = CheckpointManager(sb)
sb.run("echo state_v1 > /workspace/data.txt")
mgr.save("/tmp/ckpt_v1") # sandbox keeps running
sb.run("rm -rf /workspace/*") # destructive work
mgr.restore("/tmp/ckpt_v1") # exact rollback
output, _ = sb.run("cat /workspace/data.txt")
# "state_v1\n" — fully restored
Performance
Micro-benchmark (single sandbox)
| Docker | nitrobox | Speedup | |
|---|---|---|---|
| Create | 320ms | 25ms | 13x |
| Per command | 17ms | 11ms | 1.6x |
| Reset | 605ms | 16ms | 38x |
| Delete | 217ms | 2ms | 109x |
| Throughput | — | 94 cmd/s | — |
| Reset loop | 2.0/s | 62.8/s | 31x |
| 16x concurrent | 32 cmd/s | 655 cmd/s | 20x |
End-to-end: Terminal-Bench 2.0 (88 tasks, c=16, oracle agent, hot cache)
| Docker | nitrobox | Speedup | |
|---|---|---|---|
| Wall time | 1961.8s (33 min) | 931.2s (16 min) | 2.11x |
| Pass rate | 82/88 | 82/88 | — |
| Errors | 2 | 0 | — |
| Teardown (mean) | 15.6s | 1.1s | 14.2x |
Full benchmark: examples/results/tb2.md | Reproduce: python examples/bench_harbor_e2e.py
Docker migration cheatsheet
Auto-convert — paste your existing Docker invocation directly:
| Docker | nitrobox |
|---|---|
client.containers.run("img", cpus=0.5, ...) |
SandboxConfig.from_docker("img", cpus=0.5, ...) |
docker run --cpus=0.5 -m 512m img |
SandboxConfig.from_docker_run("docker run ...") |
docker compose up -d |
ComposeProject("docker-compose.yml").up() |
See docs/quick_start.md for full parameter mapping, compose field support, and CLI reference (nitrobox ps/kill/cleanup).
Architecture
Python API (Sandbox, ComposeProject)
├── Rust _core (pyo3) — runtime: spawn, mount, cgroup, security, pidfd
├── Go nitrobox-core (binary) — images: pull, delete (containers/storage)
└── buildkitd (daemon) — image builds: Dockerfile → layers (in-memory cache)
Image build (Dockerfile):
buildkitd (managed daemon) → in-memory cache → overlayfs snapshots
Cache hit: ~0.5s (even 16 concurrent), cold: pulls from registry
Image pull order (pre-built images):
1. containers/storage local cache → zero-copy (instant)
2. Docker daemon local → docker-daemon: transport (fast, no network)
3. Registry remote → docker:// transport (network pull)
Sandbox "worker-0"
+-- User namespace (rootless) or real root
+-- PID / Mount / UTS / IPC / Net / Time namespaces
+-- overlayfs rootfs
| +-- lowerdir: image layers from BuildKit snapshots or containers/storage
| +-- upperdir: per-sandbox changes (cleared on reset)
+-- Persistent shell (stdin/stdout pipes + signal fd for exit code)
+-- seccomp-bpf + Landlock + capability drop
+-- cgroup v2 limits + PSI monitoring
Examples
python examples/basic_usage.py # Full feature demo
python examples/bench_harbor_e2e.py # E2E Docker vs nitrobox via harbor (tb2, swebench-verified, ...)
python examples/bench_swebench.py # SWE-bench-style micro-benchmark
python examples/benchmark.py # Full performance comparison (all backends)
See docs/quick_start.md for detailed usage guide.
Known limitations
Cold image builds are slower than Docker
When a docker-compose.yml uses build: (e.g. SWE-bench tasks that FROM a prebuilt image), the first build pulls base images from the registry, which can take 30-120s depending on image size and network speed. This is comparable to Docker's cold pull time.
Once images are cached in BuildKit's in-memory content store, subsequent builds are near-instant (~0.5s per build, even with 16+ concurrent builds). For benchmarks, use --no-delete to preserve the image cache across trials.
buildkitd daemon
nitrobox runs a managed buildkitd daemon (rootless, via rootlesskit) for image builds. The daemon starts automatically on first build and persists across Python sessions for cache warmth. Use nitrobox buildkit-stop to stop it manually. It auto-restarts on next build.
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 Distributions
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 nitrobox-0.1.0.tar.gz.
File metadata
- Download URL: nitrobox-0.1.0.tar.gz
- Upload date:
- Size: 12.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22eab8fa2409bea8ed9d2176b3a72cd6d3e238183155b578080276bdf8084569
|
|
| MD5 |
63a4e310ac8a046d5273b9ae6b428bdc
|
|
| BLAKE2b-256 |
38b6c5078da9a61d471fae6452cb128a765d344e6255538b31fcdada6717f57b
|
Provenance
The following attestation bundles were made for nitrobox-0.1.0.tar.gz:
Publisher:
publish.yml on opensage-agent/nitrobox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nitrobox-0.1.0.tar.gz -
Subject digest:
22eab8fa2409bea8ed9d2176b3a72cd6d3e238183155b578080276bdf8084569 - Sigstore transparency entry: 1321473601
- Sigstore integration time:
-
Permalink:
opensage-agent/nitrobox@b8ef7056bfe207873e372ee16149173a634dfa97 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/opensage-agent
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8ef7056bfe207873e372ee16149173a634dfa97 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nitrobox-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: nitrobox-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 24.7 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1e96da720eb96a13a5b256d9fb34010b54e9cc869e2a7842485b2ca4ab2a568
|
|
| MD5 |
9acea2ec2371385ca9fdb854f340c4b4
|
|
| BLAKE2b-256 |
1904728772dd8aa78d8d5f786495ec737585563a08dd03fcb89595d211eb8b04
|
Provenance
The following attestation bundles were made for nitrobox-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
publish.yml on opensage-agent/nitrobox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nitrobox-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
b1e96da720eb96a13a5b256d9fb34010b54e9cc869e2a7842485b2ca4ab2a568 - Sigstore transparency entry: 1321473783
- Sigstore integration time:
-
Permalink:
opensage-agent/nitrobox@b8ef7056bfe207873e372ee16149173a634dfa97 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/opensage-agent
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8ef7056bfe207873e372ee16149173a634dfa97 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nitrobox-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: nitrobox-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 23.6 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b37d1da0dd1bbbe247ddf58ddb81b5fa27eedca13d89849b3b6a68e395b2b62
|
|
| MD5 |
e1359a403ade09f8dfd115d43d0078f7
|
|
| BLAKE2b-256 |
9b28f0c700edfa910d9ade4bd5171eb6fca0e9d11b122c88011fdcf1ca4ffcc5
|
Provenance
The following attestation bundles were made for nitrobox-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
publish.yml on opensage-agent/nitrobox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nitrobox-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
3b37d1da0dd1bbbe247ddf58ddb81b5fa27eedca13d89849b3b6a68e395b2b62 - Sigstore transparency entry: 1321473962
- Sigstore integration time:
-
Permalink:
opensage-agent/nitrobox@b8ef7056bfe207873e372ee16149173a634dfa97 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/opensage-agent
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8ef7056bfe207873e372ee16149173a634dfa97 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nitrobox-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: nitrobox-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 24.7 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90a7cccd0404b80fed7e6459e7fb7f2d47b6cdf338253efd1800e9c6dfe51e50
|
|
| MD5 |
c0fa6feacf76fc216f39299bf080fedb
|
|
| BLAKE2b-256 |
1d67be75cd144782254bd498bf2c468eaf671769d1a82d42e663b603f3223700
|
Provenance
The following attestation bundles were made for nitrobox-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
publish.yml on opensage-agent/nitrobox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nitrobox-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
90a7cccd0404b80fed7e6459e7fb7f2d47b6cdf338253efd1800e9c6dfe51e50 - Sigstore transparency entry: 1321473869
- Sigstore integration time:
-
Permalink:
opensage-agent/nitrobox@b8ef7056bfe207873e372ee16149173a634dfa97 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/opensage-agent
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8ef7056bfe207873e372ee16149173a634dfa97 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nitrobox-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: nitrobox-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 23.6 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba89f7409816dbec0d7b819e3d6a18ce1ce85c1848074159190490a0de9f9a84
|
|
| MD5 |
a4835ca41afd80e4bfbae99bb8e4ece6
|
|
| BLAKE2b-256 |
0e63871dd280d85b5324609972274b77a5edc76906d26778bd2b7cc8069e007b
|
Provenance
The following attestation bundles were made for nitrobox-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
publish.yml on opensage-agent/nitrobox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nitrobox-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
ba89f7409816dbec0d7b819e3d6a18ce1ce85c1848074159190490a0de9f9a84 - Sigstore transparency entry: 1321473695
- Sigstore integration time:
-
Permalink:
opensage-agent/nitrobox@b8ef7056bfe207873e372ee16149173a634dfa97 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/opensage-agent
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8ef7056bfe207873e372ee16149173a634dfa97 -
Trigger Event:
push
-
Statement type: