Quixand is a local-first sandbox and code interpreter library. It mirrors the developer-facing API of E2B’s Sandbox and AsyncSandbox while running locally by default via Docker/Podman, with a clean adapter interface to plug in other backends (your infra, remote HTTP, etc.).
- Familiar: mirrors E2B’s concepts and method names where practical
- Local-first: default execution in local containers (Docker/Podman)
- Pluggable: unified
Adapterprotocol for custom backends - Async & streaming ready; simple, secure, fast
- CLI parity for everyday workflows
- Python 3.10+
- Docker or Podman installed and in PATH
Editable install with Docker extras:
python3 -m venv .venv
. .venv/bin/activate
pip install -U pip
pip install -e '.[docker]'Quixand works out of the box, but you can configure via environment variables or code.
Environment variables:
QS_ADAPTER:local-docker(default) |chutes|remote-httpQS_TIMEOUT_DEFAULT: default timeout seconds (default 300)QS_IMAGE: default container image (defaultpython:3.11-slim)QS_RUNTIME:docker|podman(auto-detects if unset)QS_ROOT: host directory for state/template cache (default~/.quixand)QS_METADATA: JSON string to tag sandboxes
Programmatic config:
import quixand as qs
cfg = qs.Config(timeout=600, image="python:3.11-slim")Top-level imports:
from quixand import Sandbox, AsyncSandbox, connect, TemplatesSync Sandbox:
import quixand as qs
sbx = qs.Sandbox(template="python:3.11-slim", timeout=600, metadata={"user":"alice"})
sbx.files.write("hello.txt", "hi!")
print([f.path for f in sbx.files.ls(".")])
res = sbx.run(["python","-c","print(2+2)"])
print(res.text) # "4\n"
execn = sbx.run_code("x=1\nx+=1\nprint(x)")
print(execn.text.strip()) # "2"
sbx.shutdown()Async Sandbox:
import asyncio, quixand as qs
async def main():
sbx = await qs.AsyncSandbox.create(template="python:3.11-slim")
await sbx.files.put("data.csv", "/workspace/data.csv")
res = await sbx.run(["python","-c","print(42)"])
print(res.text)
await sbx.shutdown()
asyncio.run(main())Connect to running sandbox:
sbx1 = qs.Sandbox(template="python:3.11-slim")
sid = sbx1.id
sbx2 = qs.connect(sid)
print(sbx2.status())Filesystem API:
sbx.files.write("/workspace/notes.txt", "hello") # text
sbx.files.write("/workspace/blob.bin", b"\x00\x01", mode="binary")
print(sbx.files.read("/workspace/notes.txt")) # returns str
print(sbx.files.read("/workspace/blob.bin", mode="binary")) # returns bytes
sbx.files.mkdir("/workspace/data", parents=True)
sbx.files.put("./local.txt", "/workspace/local.txt")
sbx.files.get("/workspace/local.txt", "./local_copy.txt")
sbx.files.rm("/workspace/local.txt")
paths = sbx.files.glob("/workspace/*.txt")Process execution:
res = sbx.run("echo $USER && uname -a")
print(res.exit_code, res.text)Python code convenience:
sbx.install_pkg("pandas==2.2.2")
execn = sbx.run_code("import pandas as pd; print(pd.__version__)")
print(execn.ok, execn.text)Networking and ports (adapter-dependent):
binding = sbx.expose(port=8000, host_port=18000, proto="tcp")
print(binding)Timeouts and lifecycle:
st = sbx.status() # created_at, last_active_at, timeout_at
sbx.refresh_timeout(900) # extend timeout
sbx.shutdown() # stop container and clean stateThe qs CLI mirrors E2B’s verbs.
qs --help
# Sandbox lifecycle
qs sandbox create --template python:3.11-slim --timeout 900 --env FOO=bar --env BAZ=qux
qs sandbox ls
qs sandbox connect <id>
qs sandbox exec <id> -- echo hello
qs sandbox run-code <id> --code "x=1; print(x+1)"
qs sandbox refresh-timeout <id> --seconds 600
qs sandbox kill <id>
# Files
qs files ls <id> /workspace
qs files put <id> ./local.txt /workspace/local.txt
qs files get <id> /workspace/local.txt ./local_copy.txt
qs files mkdir <id> /workspace/data --parents
qs files rm <id> /workspace/local.txt --recursive
# Templates
qs templates build ./path --name py311-tools
qs templates ls
qs templates rm py311-toolsQuixand supports building images from e2b.Dockerfile or Dockerfile and caching references locally under ~/.quixand/templates.
qs templates build ./examples --name py311-tools
qs templates lsIn code:
from quixand import Templates, Sandbox
img = Templates.build("./examples", name="py311-tools")
sbx = Sandbox(template=img)Adapters implement a common protocol in quixand.adapters.base.Adapter. Included:
LocalDockerAdapter(default) — local Docker/Podman containersChutesAdapter(skeleton) — wire to your infraRemoteHTTPAdapter(skeleton) — call a remote service
Select adapter via QS_ADAPTER or passing an instance to Sandbox(adapter=...).
Quixand keeps a local registry at ~/.quixand/state.json. A lightweight watchdog process enforces idle timeouts and cleans up containers and state when deadlines are exceeded. Default timeout is 300s; refresh with refresh_timeout() or CLI.
- Non-root user inside container (image-dependent)
- No privileged flags
- Bridge network by default (configurable);
nonesupported - CPU/memory/pids limits supported by the adapter
- Ephemeral writable layer
- Docker/Podman not found: ensure one is installed and in PATH
- Permission errors: add your user to the
dockergroup (Linux) or run Docker Desktop - Hanging exec or timeouts: increase
timeoutinrun()orQS_TIMEOUT_DEFAULT - Unicode output:
CommandResult.textdecodes UTF-8 with replacement; usestdoutbytes for raw data
See examples/:
minimal.pyasync_minimal.pytemplate_build.pystreaming.py(PTY placeholder; streaming to be implemented)
- Full PTY streaming
- Richer RemoteHTTP/Chutes adapters
- Test suite and CI
MIT