Deployment of Sandbox
Aivory’s Python code execution capabilities are provided by a separate sandbox service. This page explains its sidecar architecture, the meaning of each key configuration in compose, security boundaries, and how to deploy the sandbox independently to another machine. The Python Sandbox。
Architecture: Sidecar Control + Single Session Container
The sandbox consists of two mirrors:
| The mirror | Role is |
|---|---|
ghcr.io/hjxwz123/aivory-sandbox-sidecar | Control Service (sidecar): Receives backend HTTP requests, drives Docker daemon to create/recover session containers |
ghcr.io/hjxwz123/aivory-sandbox | Runtime mirror: A locked container for each session that actually executes user code |
┌──────────┐ POST /sessions /exec /files ┌───────────────┐ docker exec ┌────────────────┐
│ app 后端 │ ────────────────────────────► │ sidecar 控制面 │ ────────────► │ 会话容器 │
└──────────┘ SANDBOX_BASE_URL └───────────────┘ │ aivory-sandbox │
└────────────────┘
sidecar itself does not execute any user code, it is loaded by /var/run/docker.sock Operate the Docker daemon host, pulling a sibling container for each session. /workspace Retention between multiple executions of the same session, the package installed by the pip, and the generated files are still aligned with the ChatGPT Code Interpreter.
The program is hosted by the host. /var/run/docker.sock In the sidecar container, the session container is actually created by the host daemon. If your environment does not allow the host socket to be loaded, you can also add a separate dind (docker:dind) daemon to the sidecar at the cost of multi-layer embedding and storage expenses. In any case, the sidecar must have access to a Docker daemon, which is a hard requirement.
Runtime mirrors have built-in data science stacks (numpy, panda, sccipy, scikit-learn, matplotlib, seaborn, plotly, etc.), document processing libraries (python-pptx, python-docx, openpyxl, reportlab, weasyprint, etc.) and Noto Sans CJK fonts, matplotlib has preconfigured in the text, and the Chinese in the chart will not display as a field.
built-in sandbox: default zero configuration
Production of composite stacks is bound. sandbox of services, docker compose up -d A command is pulled together with the sandbox, and no additional configuration is required:
- ** Not publishing any host port. ** Only only
appthrough privateinternalAccess to the Internet (SANDBOX_BASE_URL=http://sandbox:8000The public network will never be touched. - ** Internal API key **:
app与sandboxTwo services read the same.SANDBOX_API_KEYBy admissionaivory-bundled-sandboxBecause the sandbox is not externally exposed, this default value is available; but as long as you intend to expose the sandbox port to any other network, it must be changed to a strong random value. - ** Start with mirror. **:
SANDBOX_PULL_ON_START=1Let sidecar cold start when first pull the running time mirror, health checkstart_period: 120sfor this reserve. - ** Soft dependency **:
app不depends_onsandbox. sandbox is not available only when the code executes the error report, and the rest of the application works as normal.
Compose key components individually.
The following are composed. sandbox The environmental variables of the service are listed as the default values for producing compose:
| varied | The default value | Explained |
|---|---|---|
SANDBOX_API_KEY | aivory-bundled-sandbox | Bearer certification key, must be app Side consistency.sidecar no key refuses to start, test using constant time comparison |
SANDBOX_IMAGE | ghcr.io/hjxwz123/aivory-sandbox:latest | A mirror for each session. |
SANDBOX_PULL_ON_START | 1 | Pre-track time mirrors at start to ensure that the first execution does not fail. |
SANDBOX_NETWORK | none | Network of Containers. none The network is completely disconnected; bridge Connecting to the Internet (for instance) pip install) |
SANDBOX_MEMORY | 2g | Upper limit for single session containers |
SANDBOX_CPUS | 1 | Upper limit of single session container CPU |
SANDBOX_MAX_SESSIONS | 16 | Maximum number of simultaneously surviving session containers |
SANDBOX_EXEC_TIMEOUT_CAP_MS | 600000 | Upper limit of duration for single execution (10 minutes) |
SANDBOX_IDLE_TTL_CAP_SECONDS | 86400 | Upper operating hard limit for free recycling windows (24 hours) |
SANDBOX_READ_ONLY_ROOTFS | 1 | Session container root file system only reads, defensive disk filled with attacks |
SANDBOX_WORKSPACE_SIZE | 512m | Only in model. /workspace The size of tmpfs. |
SANDBOX_LOCAL_STORAGE_DIR | /var/lib/aivory/sandbox-archives | Local work area archive catalogue to zero configuration persistence with durable volumes |
SANDBOX_API_KEY: authentication shared with the app
sidecar is exposed to the ability to "drive host Docker", equivalent to host RCE, so ** Certification is compulsory. **:key for space-time sidecar directly refuses to start (the only exception is the explicit setting SANDBOX_ALLOW_NO_AUTH=1 Only reliable local development environments. app Services and sandbox The service must configure the same value to generate a strong key:
openssl rand -hex 24
SANDBOX_NETWORK: default disconnection, the cost of the network
admittedly none The session container has no network and no user code. pip install No requests can be made to change. bridge Postcode can be connected, but the cost is clear:
A networked sandbox means that the user code can send the data in the session (uploaded files, generated intermediate results) to any unexpected address, or actively scan and attack services that are accessible to the sandbox containers in your intranet unless it is explicitly required to install the package or scrape external data. none。
Upper limit of resources: MEMORY / CPUS / MAX_SESSIONS
SANDBOX_MEMORY By admission 2g) and SANDBOX_CPUS By admission 1 Limit individual session containers; SANDBOX_MAX_SESSIONS By admission 16 Limit the total number of sessions that survive at the same time.Estimate host capacity in the worst case: MAX_SESSIONS x MEMORY is the total amount of memory that the session container may occupy. SANDBOX_MAX_CONCURRENT_EXECS (Default 4) Limit the total number of simultaneous executions, SANDBOX_PIDS_LIMIT (Default 256) fork bomb resistance.
sidecar control itself is also compose limited to mem_limit: 1g / pids_limit: 512 At the time of archiving, it buffers up to 200 MiB of the work area tar package in memory (SANDBOX_MAX_ARCHIVE_BYTES), mem_limit The remaining amount should be kept above this value.
Two "hard upper limits": EXEC_TIMEOUT_CAP_MS and IDLE_TTL_CAP_SECONDS
These two variables are ** The operating ceiling. ** To work with adjustable settings for admin console:
- Execution of a single overtime set by the administrator in the background (
sandbox_exec_timeout_secwill be imprisoned.SANDBOX_EXEC_TIMEOUT_CAP_MS(within 600000ms, i.e. 10 minutes by default). - Free recovery window set by the administrator (
sandbox_idle_ttl_secwill be imprisoned.SANDBOX_IDLE_TTL_CAP_SECONDSWithin 86400s (i.e. 24 hours by default) when the background is not launched, sidecar uses a 30-minute backup to recycle free sessions.
This means that an administrator can shorten under the ceiling at any time, but it is always impossible to operate the upper limit set in compose.
Disk Defense Line: READ_ONLY_ROOTFS / WORKSPACE_SIZE
SANDBOX_READ_ONLY_ROOTFS=1 The root file system of the session container can only be read, only three locations can be written, and all tmpfs have a top size limit:
| The path | Small source | admittedly |
|---|---|---|
/workspace | SANDBOX_WORKSPACE_SIZE | 512m |
/tmp | SANDBOX_TMPFS_SIZE | 256m |
The User $HOME | SANDBOX_TMPFS_SIZE | 256m |
Therefore, it is impossible for a single session to fill the host disk, no matter how the file is written. /workspace/uploads/ The product code is written. /workspace/outputs/ All are subject. WORKSPACE_SIZE Reduce the need for larger work spaces (such as processing big data sets) SANDBOX_WORKSPACE_SIZE Note that tmpfs occupies memory.
SANDBOX_LOCAL_STORAGE_DIR: Persistence of work area archives
When the session container is recycled (free overtime or explicitly destroyed), the sidecar /workspace The tar archive is stored; the same conversation is automatically restored when the next code is executed. ** Conversation ID ** Key, so even if the session container is changed, the work area content can be recovered to survive.
SANDBOX_LOCAL_STORAGE_DIR Specify the local archive directory, and Compose has hung it to the name sandbox-archives On persistent volumes, zero configuration persistence is achieved without the need for S3/OSS/MinIO.
- This variable ** It can only be operated through environmental variables. ** Never accept values from the request or admin console (sidecar runs with root and holds docker.sock, allowing the remote to specify the path to write is equivalent to opening the host input).
- The local archive is not valid if it is left empty, and the recovery is lost (reaped = gone).
- ** Applicable for single points only. ** Normal Docker volumes are not shared across copies, and multi-copy deployments must be switched to the S3/OSS backend.
- Archiving is the best-effort: more than 200MiB of work spaces will skip archiving and log, and failure to archive/recover will not lead to failure to execute requests.
Added Admin Console
In addition to the compose environment variables, some behaviors can be performed in the admin console. Place set up. Online adjustments, no need to restart:
| The back set. | Role of | Binded |
|---|---|---|
Execution of Excess Time (sandbox_exec_timeout_sec) | The maximum time limit for each code execution | 被 SANDBOX_EXEC_TIMEOUT_CAP_MS by 制 |
The Recovery Time (sandbox_idle_ttl_sec) | How long to recycle the container? | 被 SANDBOX_IDLE_TTL_CAP_SECONDS by 制 |
The back of storage (storage_provider) | Where is the work area archive? | local (Default, i.e. the text area) s3 / aliyun_oss |
Sandbox address and key (sandbox_base_url / sandbox_api_key) | Sandbox that covers environmental variables | Environmental variables are in effect. |
Selection of storage s3 fill in endpoints, buckets and credentials; ** MinIO and any S3 compatible services ** The same choice. s3 After filling out the defined endpoint, it automatically switches to path-style address + SigV4 signature without any additional switches.
The safe border
The sandbox uses container-grade isolation, with each session container filled with a set of locks:
- ** Running without root. **,
--cap-drop ALL,--security-opt no-new-privileges。 - ** Without a network. ** (
--network none)。 - ** Read the root file system. **+ Upper limit of tmpfs (see above), plus memory/cpu/pids/nofile limit.
- Execution within the same session ** The series ** Global simultaneous distribution is limited; stdout/stderr cut to 32KB; product sheet files 20MB, up to 20 in a single time, total of 50MB.
- Possible to: Pass
SANDBOX_SECCOMP_PROFILEFix the seccomp profile to the session container (the path must be read inside the sidecar container), further narrowing the core attack surface.
The sidecar on the architecture must be able to drive the Docker daemon. /var/run/docker.sock Root equivalent on the host: Anyone who has access to the sidecar service is equal to having the host.
- ** Never expose the sidecar port to the public network. ** The built-in deployment has been done (no ports released); when independently deployed, tie the intranet interface or go VPN.
SANDBOX_API_KEYMust be a strong random value (except for the default private network for the built-in stack).- The production environment recommends placing one in front of the naked socket. docker-socket-proxy Only the container create/start/exec/kill/inspect line with image pull, all other APIs rejected, and then let the sidecar go.
DOCKER_HOST: tcp://socket-proxy:2375Do not wear a naked socket.
This is container-grade isolation, sufficient to support single machine self-deployment, but ** No is ** The HTTP protocol for sidecar is a stable contract, and deployments with higher security requirements can replace the executable backend with gVisor, Firecracker, etc. app No side changes are needed.
Additionally, the sidecar will be automatically detected when restarted. aivory.sandbox=1 Labeled inventory containers, continue to track and recycle according to TTL, will not leave orphan containers.
Independent Deployment: Sandbox is placed on another machine
Code execution is a CPU/memory-intensive load, disassembling the sandbox into a separate machine prevents it from robbing resources with the main application. sandbox services have separate public warehouses and mirrors, drawing is available without building:
** Deployment on the sandbox machine **
git clone https://github.com/hjxwz123/aivory-sandbox.git
cd aivory-sandbox
export OWNER=hjxwz123
export SANDBOX_API_KEY=$(openssl rand -hex 24)
printf 'SANDBOX_API_KEY=%s\n' "$SANDBOX_API_KEY" # 保存这个值
docker compose pull
docker compose up -d
Independent compose will publish sidecar on the host. 48217 Port (still 8000 in the container). verification:
curl -H "Authorization: Bearer $SANDBOX_API_KEY" http://localhost:48217/healthz
Return to {ok, docker, image} That is ready.
** The main application means the past. **
in the main application. .env Modify the two variables and remove (or no longer start) the built-in sandbox The Service:
SANDBOX_BASE_URL=http://<沙箱机内网地址>:48217
SANDBOX_API_KEY=<第 1 步生成的同一个值>
You can also complete the environment variables without changing them directly in the admin console. sandbox_base_url / sandbox_api_key The background value has priority over the environmental variable.
The default key of the stack (aivory-bundled-sandbox Only acceptable under the premise "Sandbox No Port Release, Only Private Network Accessible." Once the sandbox has listened to the real network card port, anyone who gets the key can drive the host's Docker. Independent deployment must: openssl rand -hex 24 Create a new key; use a firewall/security group to 48217 Restricted to accessing only the main application server; try to use an intranet or VPN link between the two machines as much as possible.
Failure inspection
| phenomenon | Inspect the direction. |
|---|---|
| The first code failed. | When the cold start is running, the mirror is not finished. docker compose logs sandbox Health inspection for 120s. start_period |
| Health tests are unhealthy. | Sidecar can't even go to Docker Daemon /var/run/docker.sock Hanging exists and host daemon is normal |
| sidecar start and exit. | SANDBOX_API_KEY Sidecar is designed to start without a key. |
in the code. pip install failed | admittedly SANDBOX_NETWORK=none No network, risk assessment after change bridge |
| The record record is full. | hit by /workspace The upper limit of tmpfs. SANDBOX_WORKSPACE_SIZE |
| Recycled workplace lost | SANDBOX_LOCAL_STORAGE_DIR Not set or the corresponding volume is not hanged; or the background storage_provider being empty. |
| Application of other functions affected | Should not occur: app Sandbox is soft-dependent, only the code execution function will report errors; if the entire station is abnormal, the problem is elsewhere |