Skip to main content

Lightweight Linux namespace sandbox with persistent shell and instant reset

This project has been archived.

The maintainers of this project have marked this project as archived. No new releases are expected.

Project description

agentdocker-lite

Tests PyPI Python License: MIT

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)agentdocker-lite
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 agentdocker_lite import Sandbox, SandboxConfig

for task in swebench_tasks:
    # Create — ~7ms (45x faster)
    sb = Sandbox(SandboxConfig.from_docker(
        task.instance_image,
    ))
    # Eval — ~11ms/cmd (1.7x faster)
    sb.run("bash /eval.sh")

    # Reset — ~7ms (82x 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
  • cgroup v2: CPU, memory, PID, IO limits with PSI pressure monitoring
  • Docker layer caching: Shared base layers across images, skip pull when cached
  • Docker Compose compatibility: Parse docker-compose.yml, per-network isolation via shared namespaces
  • CLI: adl ps/kill/cleanup for sandbox management

Requirements

  • Linux kernel 5.11+
  • util-linux (unshare)
  • Python >= 3.12

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 agentdocker-lite

Quick start

from agentdocker_lite import Sandbox, SandboxConfig

config = SandboxConfig(
    image="ubuntu:22.04",
    working_dir="/workspace",
)
sb = Sandbox(config, name="worker-0")

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
sb.delete()  # full cleanup

No sudo required. The sandbox automatically uses user namespaces for full isolation.

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")
    pids_max="256",
    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

    # 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"],
)

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()

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 agentdocker_lite 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

Crash recovery

from agentdocker_lite import SandboxBase
SandboxBase.cleanup_stale()

Performance

Single-operation latency

Docker agentdocker-lite Speedup
Create 320ms 7ms 45x
Per command 17ms 11ms 1.7x
Reset 605ms 7ms 82x
Delete 217ms 2ms 127x
Checkpoint save 12ms
Checkpoint restore 13ms

Sustained workloads

Docker agentdocker-lite Speedup
Throughput (1000 cmds) 58 cmd/s 95 cmd/s 1.6x
Reset loop (100 cycles) 2.0/s 57.1/s 29x
Checkpoint restore loop (50 cycles) 39.6/s
4x concurrent (10 cmds each) 28 cmd/s 331 cmd/s 12x
8x concurrent 31 cmd/s 622 cmd/s 20x
16x concurrent 32 cmd/s 1009 cmd/s 32x

Reproduce: python examples/benchmark.py

Docker Compose compatibility

from agentdocker_lite import ComposeProject

with ComposeProject("docker-compose.yml") as proj:
    proj.services["api"].run("curl localhost:8080/health")
    proj.reset()   # filesystem-level reset for all services

Each service runs as an independent sandbox. Services on the same networks share a network namespace (can communicate via localhost), different networks are isolated. See quick_start.md for supported compose fields.

CLI

adl ps                  # list running sandboxes
adl kill <name>         # kill and clean up a sandbox
adl kill --all          # kill all sandboxes
adl cleanup             # remove stale sandbox directories

Install: pip install agentdocker-lite provides the adl command.

Docker migration cheatsheet

Auto-convert — paste your existing Docker invocation directly:

Docker agentdocker-lite
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 --cpus=0.5 -m 512m img")
docker compose up -d ComposeProject("docker-compose.yml").up()

Manual mapping:

Docker agentdocker-lite
docker run -d ubuntu:22.04 sb = Sandbox(SandboxConfig(image="ubuntu:22.04"))
docker exec <id> echo hello sb.run("echo hello")
docker exec -it <id> bash proc = sb.popen("bash")
docker cp file.txt <id>:/path sb.copy_to("file.txt", "/path")
docker cp <id>:/path file.txt sb.copy_from("/path", "file.txt")
docker rm -f <id> && docker run -d ... sb.reset()
docker rm -f <id> sb.delete()
-v /host:/container:ro volumes=["/host:/container:ro"]
-v /host:/container:rw volumes=["/host:/container:rw"]
(no equivalent) volumes=["/host:/container:cow"]
--memory 512m memory_max="512m"
--cpus 0.5 cpu_max="0.5"
--pids-limit 256 pids_max="256"
--hostname worker-0 hostname="worker-0"
--dns 8.8.8.8 dns=["8.8.8.8"]
--read-only read_only=True
--network none net_isolate=True
-p 8080:80 net_isolate=True, port_map=["8080:80"]
docker checkpoint create (CRIU) CheckpointManager(sb).save("/path")
docker start --checkpoint CheckpointManager(sb).restore("/path")
--gpus all devices=["/dev/nvidia0", ...]
--security-opt seccomp=... seccomp=True (default)
--cpuset-cpus 0-3 cpuset_cpus="0-3"
docker compose down proj.down()
docker ps adl ps
docker kill <id> adl kill <name>

Architecture

Host kernel (shared)
  |
  +-- Sandbox "worker-0"
  |     +-- User namespace (rootless) or real root
  |     +-- PID namespace (unshare --pid)
  |     +-- Mount namespace (unshare --mount)
  |     +-- UTS namespace (custom hostname)
  |     +-- IPC namespace
  |     +-- Network namespace (optional, with pasta NAT)
  |     +-- Time namespace (for CRIU clock continuity)
  |     +-- chroot into overlayfs rootfs
  |     |     +-- lowerdir: shared base layers (read-only, cached)
  |     |     +-- upperdir: per-sandbox changes (cleared on reset)
  |     +-- Persistent bash process (stdin/stdout pipes + signal fd)
  |     +-- seccomp-bpf + Landlock + capability drop
  |     +-- cgroup v2 limits + PSI monitoring
  |
  +-- Sandbox "worker-1"
  |     +-- (same structure, independent namespaces)
  ...

Examples

python examples/basic_usage.py      # Full feature demo
python examples/bench_swebench.py   # SWE-bench-style Docker vs adl comparison
python examples/benchmark.py        # Full performance comparison (all backends)

See docs/quick_start.md for detailed usage guide.

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

agentdocker_lite-0.0.5.tar.gz (2.6 MB view details)

Uploaded Source

Built Distribution

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

agentdocker_lite-0.0.5-py3-none-any.whl (2.5 MB view details)

Uploaded Python 3

File details

Details for the file agentdocker_lite-0.0.5.tar.gz.

File metadata

  • Download URL: agentdocker_lite-0.0.5.tar.gz
  • Upload date:
  • Size: 2.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for agentdocker_lite-0.0.5.tar.gz
Algorithm Hash digest
SHA256 771f31f56458686a2962cc821d33bcc9a12330c165ca34f93adee8fd8990b0ed
MD5 088c2a1a96dfc307237750a22ffdd4dd
BLAKE2b-256 1723ef6b962ec605f6c52f05cda4b0dda2429c8cbabf5b1d2e9de17ff80b2a8d

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentdocker_lite-0.0.5.tar.gz:

Publisher: publish.yml on opensage-agent/agentdocker-lite

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

File details

Details for the file agentdocker_lite-0.0.5-py3-none-any.whl.

File metadata

File hashes

Hashes for agentdocker_lite-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 6b61786913bbd48e55ac8edd963242fdb928fcdcf3df9ceab495c76f5b4224b2
MD5 7552cfda4d098be4ad7cf2369fe87b2c
BLAKE2b-256 d807cf686f0c58e80c9053b8eb4ce9355aca8c6e5d8f17b666c6386bb96b8db3

See more details on using hashes here.

Provenance

The following attestation bundles were made for agentdocker_lite-0.0.5-py3-none-any.whl:

Publisher: publish.yml on opensage-agent/agentdocker-lite

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