@axiom-lattice/opensandbox-gateway
v0.2.1
Published
`@axiom-lattice/opensandbox-gateway` is an API-compatible replacement for `@axiom-lattice/microsandbox-service` that uses [OpenSandbox](https://github.com/opensandbox-group/OpenSandbox) as the sandbox backend.
Downloads
347
Readme
@axiom-lattice/opensandbox-gateway
@axiom-lattice/opensandbox-gateway is an API-compatible replacement for @axiom-lattice/microsandbox-service that uses OpenSandbox as the sandbox backend.
It exposes the same HTTP API — Core's MicrosandboxRemoteProvider and MicrosandboxServiceClient work without any code changes.
Prerequisites
- Node.js 18+
- Docker or OrbStack
- Python 3.10+ with uv
- OpenSandbox Server (
uv tool install opensandbox-server)
OpenSandbox Server Setup
The gateway requires a running OpenSandbox Server instance.
Install
uv tool install opensandbox-serverConfigure
Option A: No config (simplest, for local dev)
Start without a config file — defaults to Docker runtime on 0.0.0.0:8080:
OPENSANDBOX_INSECURE_SERVER=YES opensandbox-serverOPENSANDBOX_INSECURE_SERVER=YES is required when api_key is not set (safe for local dev only).
Option B: Config file (recommended for production)
# Generate a Docker-mode config template
opensandbox-server init-config ~/.sandbox.toml --example docker
# Edit the file to set api_key, port, etc.
# Then start:
opensandbox-server --config ~/.sandbox.tomlKey ~/.sandbox.toml settings:
[server]
host = "0.0.0.0"
port = 8080
api_key = "your-secret-key" # Required for production
[runtime]
type = "docker" # "docker" or "kubernetes"
[runtime.docker]
network_mode = "host" # "host" or "bridge"
# Required for volume mounts (named volumes from Core)
[storage]
allowed_host_paths = ["/root/.opensandbox/volumes"]Verify
curl http://localhost:8080/health
# → {"status":"healthy"}Full server documentation: OpenSandbox Server docs
Quick Start
# 1. Start OpenSandbox Server (Docker mode, no config)
OPENSANDBOX_INSECURE_SERVER=YES opensandbox-server &
# → http://localhost:8080
# 2. Build and start the gateway
pnpm --filter @axiom-lattice/opensandbox-gateway build
OPEN_SANDBOX_DOMAIN=localhost:8080 node packages/opensandbox-gateway/dist/cli.mjs &
# → http://localhost:4002
# 3. Verify
curl http://localhost:4002/healthInstall and Build
pnpm --filter @axiom-lattice/opensandbox-gateway buildCLI Usage
lattice-opensandbox-gateway --host 0.0.0.0 --port 4002Supported flags:
--host <string>--port <number>
Configuration
Runtime configuration is resolved in this order: CLI arguments → environment variables → defaults.
Service Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| HOST | 0.0.0.0 | Listen address |
| PORT | 4002 | Listen port |
| GATEWAY_API_KEY | — | Bearer token for authenticating to this service |
| MICROSANDBOX_API_KEY | — | Fallback alias for GATEWAY_API_KEY (backward compatibility) |
| BODY_LIMIT | 104857600 (100MB) | Max request body size |
Authentication
When GATEWAY_API_KEY (or MICROSANDBOX_API_KEY) is set, all /api/* endpoints require a Bearer token:
# Without key → 401
curl http://localhost:4003/api/sandboxes
# → {"success":false,"error":{"code":"UNAUTHORIZED","message":"Unauthorized"}}
# With key → 200
curl -H "Authorization: Bearer your-key" http://localhost:4003/api/sandboxes
# → {"success":true,"data":{...}}The /health endpoint and Swagger UI (/api-docs) are not authenticated.
On the Core side, set MICROSANDBOX_API_KEY environment variable to match.
OpenSandbox Connection
| Variable | Default | Description |
|----------|---------|-------------|
| OPEN_SANDBOX_DOMAIN | localhost:8080 | OpenSandbox Server address |
| OPEN_SANDBOX_PROTOCOL | http | http or https |
| OPEN_SANDBOX_API_KEY | — | API key for OpenSandbox Server |
| OPEN_SANDBOX_USE_SERVER_PROXY | false | Route execd traffic through server proxy |
Sandbox Defaults
| Variable | Default | Description |
|----------|---------|-------------|
| OPEN_SANDBOX_DEFAULT_IMAGE | ubuntu:22.04 | Docker image for new sandboxes |
| OPEN_SANDBOX_DEFAULT_CPUS | 1 | vCPUs per sandbox |
| OPEN_SANDBOX_DEFAULT_MEMORY_MIB | 2048 | Memory in MiB per sandbox |
| OPEN_SANDBOX_DEFAULT_TIMEOUT | 600 | Auto-kill sandbox after N seconds of inactivity. Set high or use null via API for manual cleanup |
| SANDBOX_IDLE_TIMEOUT_MS | 600000 | Max cache TTL before rebuilding on next ensure |
Volume Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| VOLUME_BASE_PATH | ~/.opensandbox/volumes | Host directory for named volume filesystem operations |
Named volumes from Core (e.g. s<hash> for skills, p<hash> for projects) are mapped to host directories under $VOLUME_BASE_PATH/<name>/. The OpenSandbox Server must allow this path via [storage] allowed_host_paths in ~/.sandbox.toml:
[storage]
allowed_host_paths = ["/root/.opensandbox/volumes"]Without this, volume mounts will fail with "Host path is not under any allowed prefix".
Startup Examples
Start with CLI flags:
lattice-opensandbox-gateway --host 127.0.0.1 --port 4002Start with environment variables:
OPEN_SANDBOX_DOMAIN=opensandbox.internal:8080 OPEN_SANDBOX_DEFAULT_IMAGE=python:3.11 lattice-opensandbox-gatewayZero-config start (all defaults):
lattice-opensandbox-gatewayConnecting from Core
Set the same MICROSANDBOX_SERVICE_URL used for microsandbox-service:
MICROSANDBOX_SERVICE_URL=http://localhost:4002The MicrosandboxRemoteProvider → MicrosandboxServiceClient → HTTP → gateway chain works identically.
Process Manager Examples
systemd
[Unit]
Description=Axiom Lattice OpenSandbox Gateway
After=network.target opensandbox-server.service
[Service]
ExecStart=/usr/local/bin/lattice-opensandbox-gateway --host 0.0.0.0 --port 4002
Restart=on-failure
Environment=OPEN_SANDBOX_DOMAIN=localhost:8080
[Install]
WantedBy=multi-user.targetsupervisor
[program:opensandbox-gateway]
command=/usr/local/bin/lattice-opensandbox-gateway --host 0.0.0.0 --port 4002
autostart=true
autorestart=true
environment=OPEN_SANDBOX_DOMAIN="localhost:8080"docker
CMD ["lattice-opensandbox-gateway", "--host", "0.0.0.0", "--port", "4002"]Local Development
pnpm --filter @axiom-lattice/opensandbox-gateway devDocker Compose Deployment
Deploys the full sandbox stack (opensandbox-server + opensandbox-gateway) with a single command. Files live in this package:
docker-compose.yml— both services, network, and volume wiringsandbox.toml— OpenSandbox Server config (mounted into the server container)Dockerfile— gateway image build (pnpm install → build →pnpm deploy --prod).env.example— secret template; copy to.envand fill in
Architecture
Core (agent) Docker host
│ MICROSANDBOX_SERVICE_URL │
▼ │
opensandbox-gateway ──HTTP──▶ opensandbox-server ──docker.sock──▶ sandbox containers
(Node Fastify, :4002) (Python FastAPI, :8080)Prerequisites
- Docker Engine with compose support (Docker Desktop on macOS)
- On macOS,
VOLUME_DIRmust be under a Docker Desktop shared path (e.g. a dir under/Users)
Configure
- Publish the gateway image once (it has no official image), then set
.env:
Thecp .env.example .env # edit .env → SANDBOX_API_KEY, GATEWAY_IMAGE, VOLUME_DIRGATEWAY_IMAGEmust point to an image built from this package'sDockerfile(see the deploy package README for the build/push commands). - (Optional) Override defaults via the compose
environmentorsandbox.toml:- Business sandbox image — set on the Core side via
MICROSANDBOX_IMAGE(defaultkioko12520/sandbox:0.1.0). The gateway fallback isOPEN_SANDBOX_DEFAULT_IMAGE(defaultubuntu:22.04). execd_image/egress.imageinsandbox.tomlmust be pullable on the host.
- Business sandbox image — set on the Core side via
Linux hosts:
sandbox.tomlships with[proxy] resolve_internal = false+[docker] host_ip = "172.17.0.1"(host-mapped routing). Keep them — with the defaultresolve_internal = true, the Linux server container cannot route to sandbox bridge IPs and sandbox operations hang (OrbStack/macOS tolerates it; Linux does not). Seedeploy/OFFLINE_DEPLOYMENT.md.
Start
cd packages/opensandbox-gateway
docker compose up -dVerify:
curl http://localhost:8080/health # OpenSandbox Server → {"status":"healthy"}
curl http://localhost:4002/health # gateway
curl -H "Authorization: Bearer $SANDBOX_API_KEY" http://localhost:4002/api/sandboxesConnect from Core
On the agent process, set:
MICROSANDBOX_SERVICE_URL=http://localhost:4002
MICROSANDBOX_API_KEY=<same as SANDBOX_API_KEY>
MICROSANDBOX_IMAGE=kioko12520/sandbox:0.1.0 # optional, dynamic sandbox imagePath alignment (important)
VOLUME_DIR (host) is bind-mounted into the gateway at /data/opensandbox/volumes, and the server only allows bind mounts under allowed_host_paths in sandbox.toml. All three must agree:
| Place | Value |
|-------|-------|
| .env → VOLUME_DIR (host, Docker daemon view) | /data/opensandbox/volumes (default) |
| Gateway VOLUME_BASE_PATH (inside container, fixed) | /data/opensandbox/volumes |
| sandbox.toml → allowed_host_paths | /data/opensandbox/volumes (default) |
If you change VOLUME_DIR, update allowed_host_paths in sandbox.toml to the same value. A mismatch produces Host path is not under any allowed prefix on volume mounts.
Auth
SANDBOX_API_KEY is shared by both services (server api_key + gateway's server-auth key). The gateway's own auth defaults to the same key and can be overridden with GATEWAY_API_KEY. The Core side must set MICROSANDBOX_API_KEY to the gateway's auth key.
Operational Notes
- Requires a running OpenSandbox Server. Start it before starting the gateway.
- Invalid startup configuration exits non-zero.
SIGINTandSIGTERMtrigger graceful shutdown.- Image management endpoints (
GET /api/images,POST /api/images/pull) are stubs and return empty results. - Sandbox logs and runtime metrics are not yet implemented (stub returns).
- Volume FS operations (
/api/volumes/:name/fs/*) read/write files on the host at$VOLUME_BASE_PATH. Works with Docker runtime where the gateway has filesystem access to the host. - Sandbox TTL: sandboxes expire after
OPEN_SANDBOX_DEFAULT_TIMEOUTseconds (default600) of inactivity. The server destroys the container on expiry.PUT /api/sandboxes/:name(ensure) recreates an expired sandbox, but file operations do not auto-recreate — they fail against a dead container. This is transparent to the Core agent (it ensures before every operation), but note that container filesystem state is lost on rebuild unless data lives in named volumes. For long-running workloads, raiseOPEN_SANDBOX_DEFAULT_TIMEOUTor use named volumes for persistence.
API Compatibility
This service exposes the same REST API as @axiom-lattice/microsandbox-service. All endpoints return the same JSON envelope ({ success: true, data: ... } or { success: false, error: { code, message } }).
See the Swagger UI at http://localhost:4002/api-docs after starting the service.
