Skip to main content

Lightweight Linux namespace sandbox with persistent shell and instant reset

Project description

nitrobox

Tests codecov PyPI Python License

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 pasta binary 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-stop for 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nitrobox-0.1.0.tar.gz (12.1 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

nitrobox-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (24.7 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

nitrobox-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.6 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ ARM64

nitrobox-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (24.7 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

nitrobox-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.6 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ ARM64

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

Hashes for nitrobox-0.1.0.tar.gz
Algorithm Hash digest
SHA256 22eab8fa2409bea8ed9d2176b3a72cd6d3e238183155b578080276bdf8084569
MD5 63a4e310ac8a046d5273b9ae6b428bdc
BLAKE2b-256 38b6c5078da9a61d471fae6452cb128a765d344e6255538b31fcdada6717f57b

See more details on using hashes here.

Provenance

The following attestation bundles were made for nitrobox-0.1.0.tar.gz:

Publisher: publish.yml on opensage-agent/nitrobox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nitrobox-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for nitrobox-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b1e96da720eb96a13a5b256d9fb34010b54e9cc869e2a7842485b2ca4ab2a568
MD5 9acea2ec2371385ca9fdb854f340c4b4
BLAKE2b-256 1904728772dd8aa78d8d5f786495ec737585563a08dd03fcb89595d211eb8b04

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nitrobox-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for nitrobox-0.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3b37d1da0dd1bbbe247ddf58ddb81b5fa27eedca13d89849b3b6a68e395b2b62
MD5 e1359a403ade09f8dfd115d43d0078f7
BLAKE2b-256 9b28f0c700edfa910d9ade4bd5171eb6fca0e9d11b122c88011fdcf1ca4ffcc5

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nitrobox-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for nitrobox-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 90a7cccd0404b80fed7e6459e7fb7f2d47b6cdf338253efd1800e9c6dfe51e50
MD5 c0fa6feacf76fc216f39299bf080fedb
BLAKE2b-256 1d67be75cd144782254bd498bf2c468eaf671769d1a82d42e663b603f3223700

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nitrobox-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for nitrobox-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 ba89f7409816dbec0d7b819e3d6a18ce1ce85c1848074159190490a0de9f9a84
MD5 a4835ca41afd80e4bfbae99bb8e4ece6
BLAKE2b-256 0e63871dd280d85b5324609972274b77a5edc76906d26778bd2b7cc8069e007b

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page