This project has been archived by its maintainers, and is no longer receiving any updates.
agentdocker-lite
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
pastabinary 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/cleanupfor 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.
Release files for agentdocker-lite 0.0.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agentdocker_lite-0.0.5.tar.gz | 2.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentdocker_lite-0.0.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 5.1 MB
Release files / agentdocker_lite-0.0.5.tar.gz
| Download URL | agentdocker_lite-0.0.5.tar.gz |
|---|---|
| Size | 2.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
771f31f56458686a2962cc821d33bcc9a12330c165ca34f93adee8fd8990b0ed
|
|
BLAKE2b-256 checksum How to use checksums |
1723ef6b962ec605f6c52f05cda4b0dda2429c8cbabf5b1d2e9de17ff80b2a8d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 24, 2026.
Transparency logRelease files / agentdocker_lite-0.0.5-py3-none-any.whl
| Download URL | agentdocker_lite-0.0.5-py3-none-any.whl |
|---|---|
| Size | 2.5 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6b61786913bbd48e55ac8edd963242fdb928fcdcf3df9ceab495c76f5b4224b2
|
|
BLAKE2b-256 checksum How to use checksums |
d807cf686f0c58e80c9053b8eb4ce9355aca8c6e5d8f17b666c6386bb96b8db3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 24, 2026.
Transparency log