Repository navigation
Code Interpreter with OpenSandbox
The Sandbox builtin (identifier lobe-sandbox, formerly Code Interpreter) gives the model a Linux container for the conversation. OpenSandbox is the only backend. The DifySandbox backend, SANDBOX_PROVIDER, and CODE_INTERPRETER_SANDBOX_URL / _API_KEY are gone.
The model can run shell commands (including background servers), git, Node.js, and Python, and it can read, write, edit, list, and export files. The working directory is /tmp/workspace. Files, installs, and background processes carry over between calls in that conversation. Python variables do not. Graphile runs these calls after you close the tab. Enabling Sandbox does not force the turn onto the browser.
Leave the server unset if you do not need it. ChatHub still boots. A Sandbox call then returns a configuration error and the model continues.
Setup below is a single-host Docker deployment with runc. Kata is optional and untested. Full procedure for the image, the server, and ChatHub's env follows. Maintainer notes live in the repository under openwiki/architecture/sandbox.md.
| API | Use |
|---|---|
runCommand |
A bash command. background: true starts a server or watcher and returns a command id. |
getCommandOutput / stopCommand
|
Read or stop a background command. |
runPython |
Python in a new process. The old API name is python. |
readFile / writeFile / editFile / listFiles
|
Files in the sandbox. Relative paths start at /tmp/workspace. An absolute path is inside the container, not on the ChatHub host. |
exportFile |
Copy sandbox files into the chat so the user can open them. |
packages on runPython does not install anything. Use runCommand with pip or npm, or add the package to the image. An install lasts only while that sandbox lives.
In the installed app, tap the file link in the reply, or Download on the file card. A reply link that points at public storage uses the same save. iOS opens the share sheet. Choose Save to Files. The chat stays open.
If the file takes a moment to prepare, the app asks you to confirm, then opens the share sheet. Desktop browsers and desktop installed apps download the file directly.
- A ChatHub image that includes this Sandbox tool.
- Root on the Docker host.
- A free subnet for sandboxes. This guide uses
172.30.0.0/24. - A free host port range. This guide uses
40000-41000. - The Docker network ChatHub already uses. This guide uses
1panel-networkas the example. The OpenSandbox server must join that network. Do not publish the server port.
Sandboxes use their own bridge, with inter-container traffic off.
docker network inspect $(docker network ls -q) \
--format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}' | grep '172\.30\.' || true
docker network create --driver bridge --subnet 172.30.0.0/24 \
--opt com.docker.network.bridge.enable_icc=false chathub-sbxIf the first command prints a line, pick another subnet and use it everywhere this guide says 172.30.0.0/24 and 172.30.0.1.
These iptables rules keep sandbox code off the host, private ranges, cloud metadata (169.254.0.0/16), and other sandboxes. They are lost on reboot, so install them as a service. Docker's DOCKER-USER chain must exist (iptables -S DOCKER-USER prints -N DOCKER-USER).
cat > /usr/local/sbin/chathub-sbx-firewall.sh <<'EOF'
#!/bin/sh
set -eu
SBX=172.30.0.0/24
CHAIN=CHATHUB-SBX
iptables -N "$CHAIN" 2>/dev/null || iptables -F "$CHAIN"
iptables -A "$CHAIN" -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
iptables -A "$CHAIN" -d 10.0.0.0/8 -j DROP
iptables -A "$CHAIN" -d 172.16.0.0/12 -j DROP
iptables -A "$CHAIN" -d 192.168.0.0/16 -j DROP
iptables -A "$CHAIN" -d 169.254.0.0/16 -j DROP
iptables -C DOCKER-USER -s "$SBX" -j "$CHAIN" 2>/dev/null \
|| iptables -I DOCKER-USER 1 -s "$SBX" -j "$CHAIN"
iptables -C INPUT -s "$SBX" -m conntrack --ctstate NEW -j DROP 2>/dev/null \
|| iptables -I INPUT 1 -s "$SBX" -m conntrack --ctstate NEW -j DROP
EOF
chmod 0755 /usr/local/sbin/chathub-sbx-firewall.sh
cat > /etc/systemd/system/chathub-sbx-firewall.service <<'EOF'
[Unit]
Description=Firewall rules for the ChatHub OpenSandbox network
After=docker.service
PartOf=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/chathub-sbx-firewall.sh
[Install]
WantedBy=docker.service
EOF
systemctl daemon-reload
systemctl enable --now chathub-sbx-firewall.serviceBuild docker/opensandbox from this repository on the host that runs OpenSandbox. The tag below is what ChatHub sends as OPENSANDBOX_IMAGE.
docker build -t chathub-sandbox:1 docker/opensandbox
docker run --rm chathub-sandbox:1 python3 -c "import pandas, matplotlib, reportlab; print('image ok')"The image is python:3.12-slim plus Node.js (npm, pnpm, yarn), git, and common CLIs (curl, jq, ripgrep, sqlite3, zip). It includes numpy, pandas, matplotlib, and the office libraries the prompt prefers when they are installed, plus a STSong.ttf path for Chinese PDFs. The official opensandbox/code-interpreter image is not this image.
To add a package, edit docker/opensandbox/requirements.txt (or the apt list in the Dockerfile) and rebuild. A pip install inside a call is not a substitute: it disappears when that sandbox is removed.
The image must already be on the host. ChatHub does not send registry credentials.
Tested server images: opensandbox/server:release-1.1.0 and release-1.1.1-rc.1. execd image: opensandbox/execd:v1.1.0. publish_host needs 1.1.1 or later so sandbox ports are not published on public interfaces.
docker pull opensandbox/execd:v1.1.0
mkdir -p ~/chathub/opensandbox~/chathub/opensandbox/config.toml:
[server]
host = "0.0.0.0"
port = 8090
max_sandbox_timeout_seconds = 3600
[proxy]
resolve_internal = false
[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.1.0"
[docker]
network_mode = "chathub-sbx"
host_ip = "172.30.0.1"
publish_host = "172.30.0.1"
port_range_min = 40000
port_range_max = 41000
no_new_privileges = true
pids_limit = 4096
[ingress]
mode = "direct"Server 1.1.0 defaults network_mode to host. Set it. Do not add a [secure_runtime] block if you want runc.
OPENSANDBOX_EGRESS_ALLOW (a deny-by-default domain list) needs [docker] network_mode = "bridge" and the egress sidecar. It is rejected on a user-defined network such as chathub-sbx. Leave the allowlist unset with the network in this guide, and rely on the firewall from step 1.
~/chathub/opensandbox.env (mode 600):
cd ~/chathub
[ -f opensandbox.env ] || { printf 'SANDBOX_CONFIG_PATH=/etc/opensandbox/config.toml\nOPENSANDBOX_SERVER_API_KEY=%s\n' "$(openssl rand -hex 32)" > opensandbox.env; chmod 600 opensandbox.env; }Compose service. The data volume keeps renewed expiry across server recreates. Do not publish a port.
opensandbox:
image: opensandbox/server:release-1.1.1-rc.1
container_name: chathub-opensandbox
restart: always
env_file:
- opensandbox.env
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./opensandbox/config.toml:/etc/opensandbox/config.toml:ro
- ./opensandbox/data:/root/.opensandbox
networks:
- 1panel-network
security_opt:
- no-new-privileges:truedocker compose up -d opensandbox
docker logs chathub-opensandbox 2>&1 | tail -3Expect Uvicorn on http://0.0.0.0:8090.
max_sandbox_timeout_seconds applies only when a sandbox is created, not when ChatHub renews it. The default create lifetime is about 240 seconds (60s ready + 60s call + 120s). A call budget above 3420000 ms with the default ready budget makes that create longer than 3600 seconds and every create returns HTTP 400. Raise the server setting, or lower SANDBOX_TIMEOUT / SANDBOX_MAX_TIMEOUT.
Put these in ChatHub's env file (lobe.env on 1Panel) or its environment: block. OPENSANDBOX_API_KEY must equal OPENSANDBOX_SERVER_API_KEY.
| Variable | Required | Purpose |
|---|---|---|
OPENSANDBOX_SERVER_URL |
yes |
http://chathub-opensandbox:8090 on the shared Docker network |
OPENSANDBOX_API_KEY |
yes | Same value as the server's OPENSANDBOX_SERVER_API_KEY
|
OPENSANDBOX_IMAGE |
yes | chathub-sandbox:1 |
OPENSANDBOX_CPU / OPENSANDBOX_MEMORY
|
no | Defaults 1 and 2Gi
|
OPENSANDBOX_READY_TIMEOUT |
no | Create plus execd ready, milliseconds (default 60000) |
OPENSANDBOX_SESSION_IDLE_TIMEOUT |
no | Keep the conversation's sandbox this long after the last call, milliseconds (default 1800000). 0 starts a fresh sandbox every call and disables background commands |
OPENSANDBOX_SESSION_MAX_LIFETIME |
no | Optional age cap in milliseconds (default 0, no cap) |
SANDBOX_TIMEOUT |
no | Default per-call limit, milliseconds (default 60000) |
SANDBOX_MAX_TIMEOUT |
no | Longest limit the model may request (default 600000) |
SANDBOX_MAX_FILE_BYTES |
no | Per-file cap (default 10 MiB) |
SANDBOX_MAX_FILE_COUNT |
no | Default 20
|
SANDBOX_MAX_OUTPUT_CHARS |
no | stdout and stderr kept per call (default 30000) |
If a SANDBOX_* limit is unset, ChatHub still reads the old name: CODE_INTERPRETER_TIMEOUT, CODE_INTERPRETER_MAX_FILE_BYTES, CODE_INTERPRETER_MAX_FILE_COUNT, or CODE_INTERPRETER_MAX_STDOUT_CHARS.
Recreate the ChatHub container after editing the env file. Enable Sandbox in the assistant tool picker.
Ordinary chat messages do not restart the idle timer. Only Sandbox calls do. Stop during a call cancels the turn. It does not leave a failed tool result for the model to continue from.
Remove the code-interpreter service and its depends_on entry in the same edit. Compose will not start ChatHub if depends_on names a service that is gone.
Delete SANDBOX_PROVIDER, CODE_INTERPRETER_SANDBOX_URL, and CODE_INTERPRETER_SANDBOX_API_KEY. Set the OPENSANDBOX_* variables above. There is no provider switch and no rollback flag.
| What you see | What to do |
|---|---|
| Tool says the sandbox is not configured | Set both OPENSANDBOX_SERVER_URL and OPENSANDBOX_IMAGE, then recreate ChatHub |
| HTTP 401 from the server |
OPENSANDBOX_API_KEY does not match OPENSANDBOX_SERVER_API_KEY
|
| HTTP 400 on create, mentioning the maximum timeout | Create TTL is over max_sandbox_timeout_seconds. Lower SANDBOX_TIMEOUT / SANDBOX_MAX_TIMEOUT, or raise the server setting |
| Call timed out | The command ran longer than SANDBOX_TIMEOUT (or the model's timeout, capped by SANDBOX_MAX_TIMEOUT) |
ModuleNotFoundError after the chat went idle |
The sandbox was removed. Install again, raise OPENSANDBOX_SESSION_IDLE_TIMEOUT, or add the package to the image |
| Background command refused |
OPENSANDBOX_SESSION_IDLE_TIMEOUT=0 (a fresh sandbox every call) |
| Sandbox still listed after a successful call | Expected until the idle timeout. sandbox_cleanup_failed means a delete or renew failed; the server TTL still reaps it |
- Home
- Quick Start
- Docker Deployment and Upgrades
- Authentication
- Model Providers
- Knowledge Base and RAG
- MarkItDown Sidecar
- Sandbox (OpenSandbox)
- OpenAI Compatible Cache Matrix
- Model Extension Options
- Memory & Context Compaction
- Background Conversation Generation
- Pasted Text
- Context Export
- Data Backup and Restore
- Image Generation
- Artifacts
- Inline SVG Diagrams
- Tools Hub
- MCP OAuth
- MCP Management
- Agent Skills
- Database
- Development
- Debug and Diagnostics
- Architecture Overview
- Docker Release
- Release Workflow