OpenCode is a powerful AI coding assistant β but by default it runs on your host machine with broad access to your environment, shared session state, and a single global configuration for all projects.
opencode-sandbox runs OpenCode inside a Docker container, giving each project its own isolated environment:
- π Scoped access β OpenCode can only see the project workspace, nothing else on your machine
- π§© Per-project configuration β AI providers, API keys, and model settings are configured independently per project
- πΎ Persistent session state β each project retains its own OpenCode history and session between container restarts
- π§Ή Clean environment β no bleed-over between projects; rebuild any time for a fresh start
opencode-sandbox uses mise to manage software inside the container. mise does not need to be installed on your host machine, though using it on the host as well is recommended for a consistent toolchain experience.
The following setups have been tested. Other combinations will most likely work too β if you try one, please report your experience.
| Host OS | Container Runtime | Status |
|---|---|---|
| macOS | Colima | β Tested |
| macOS | Podman | β Tested |
| Linux | Docker | β Tested |
- A Docker-compatible container runtime (e.g. Docker, Podman, Colima)
- opencode if you want to use opencode via a local terminal client instead of the web UI (optional, but recommended for a seamless experience)
Clone this repository and add the bin/ directory to your PATH:
git clone https://github.com/comsysto/opencode-sandbox.git
export PATH="/path/to/opencode-sandbox/bin:$PATH"Add the export line to your shell profile (.zshrc, .bashrc, etc.) to make it permanent.
That's it β the ocs-* commands are now available globally.
Once installed, run the following from the root of your project to initialize and set up the sandbox:
ocs-initThis will interactively create mise.toml, opencode-sandbox-config.yaml, opencode.jsonc, opencode-sandbox-pre-start-container.sh, update .gitignore, and build the container β confirming each step before acting.
Then start it:
ocs-start-containerocs-init and ocs-rebuild-container must be run from the project root. All other commands can be run from the project root or any subdirectory inside the same project.
Initializes a target project for use with opencode-sandbox. Run once from the project root.
Interactively creates (each step skipped if already present, default answer is yes):
mise.tomlβ minimal config with opencode onlyopencode-sandbox-config.yamlβ YAML config pre-filled withsandbox-name(set to the project directory name) and controls for outbound HTTP/HTTPS whitelist, host TCP ports, and env var passthroughopencode.jsoncβ OpenCode model, provider, and permission configopencode-sandbox-pre-start-container.shβ empty hook script sourced before the container starts (see Hooks)- Builds the Docker container image
Prepares build artifacts and rebuilds the Docker image. Run this whenever you want a fresh image (new password, updated mise.toml, updated opencode-sandbox-config.yaml, etc.).
- Reads
sandbox-namefromopencode-sandbox-config.yamland combines it with a short hash of the project root path to form aSANDBOX_ID(e.g.my-project-a3f92c), then names the containeropencode-sandbox-<SANDBOX_ID> - Creates
~/.opencode-sandbox/<SANDBOX_ID>/for build artifacts and persistent state - Generates a random server password saved to
~/.opencode-sandbox/<SANDBOX_ID>/opencode-passwordwith owner-only permissions - Copies
mise.tomlinto the build context - Parses
opencode-sandbox-config.yamlinto derived build artifacts - Builds the Docker image
Starts the sandbox for the current project. Each invocation creates a fresh container (--rm ensures it is removed on stop); session state is preserved between runs because the workspace and OpenCode state are mounted volumes.
- Sources
opencode-sandbox-pre-start-container.shfrom the project root, if it exists (see Hooks) - Forwards whitelisted host environment variables into the container (as configured in
opencode-sandbox-config.yaml) - Mounts your project root as
/<dirname>inside the container (e.g. a project at/home/user/my-projectis mounted at/my-project) - Exposes OpenCode on
http://127.0.0.1:4096 - Press
Ctrl+Cto stop and remove the container
Opens http://127.0.0.1:4096 in your default browser on macOS or Linux.
Use ocs-web-auth for authentication or authenticate manually in the browser when prompted:
- Username:
opencode - Password: contents of
~/.opencode-sandbox/<SANDBOX_ID>/opencode-password
Opens the browser with credentials embedded in the URL (basic auth) on macOS or Linux. Use this on first visit to authenticate your browser session.
Note: After authenticating, the page may show a JavaScript error β this is expected. Your session is authenticated; use
ocs-webto open a clean working tab.
Attaches an OpenCode terminal session to the running container.
| Situation | Command |
|---|---|
| First time setup | ocs-init then ocs-start-container |
| Daily use | ocs-start-container |
After changing mise.toml |
ocs-rebuild-container then ocs-start-container |
After changing opencode-sandbox-config.yaml |
ocs-rebuild-container then ocs-start-container |
| Stop the container | Ctrl+C in the ocs-start-container terminal (container is removed) |
Note: Each
ocs-start-containerrun creates a fresh container that is removed on stop. Session state is preserved between runs via mounted volumes (workspace and~/.opencode-sandbox/<SANDBOX_ID>/opencode-state/).ocs-rebuild-containerrebuilds the image but does not affect the mounted state.
The container runs an internal Squid proxy that restricts outbound HTTP/HTTPS traffic to an explicit whitelist. iptables rules inside the container use a default-deny outbound policy and allow only loopback traffic, established connections, and Squid's own DNS + HTTP/HTTPS egress. OpenCode (and any tools it spawns) must go through the proxy.
All outbound traffic is routed via the proxy automatically through the standard http_proxy / https_proxy environment variables set by the container entrypoint.
Note: The container requires the
NET_ADMINDocker capability foriptablesβ this is added automatically byocs-start-container.
The opencode-sandbox-config.yaml file in your project root controls the project name, outbound network access, and environment variables. It is safe to commit.
Note: Only a narrow YAML subset is supported: top-level keys, one-level-deep list items (
- value), and one-level-deep map entries (key: value). Anchors, multi-line strings, nested structures, and other YAML features are not supported.
sandbox-name: my-project
http-domain-whitelist:
- .github.com
- api.anthropic.com
- registry.npmjs.org
host-ports:
- 5432
- 6379
env-passthrough:
ANTHROPIC_API_KEY: ANTHROPIC_API_KEY
GH_TOKEN: MY_PROJECT_GH_TOKEN
env:
GITHUB_REPOSITORY: my-org/my-reposandbox-name β human-readable project identifier (required):
- Committed to the repository as a stable label for the project
- At runtime, combined with a short hash of the absolute project root path to form
SANDBOX_ID(e.g.my-project-a3f92c), making collisions between multiple checkouts of the same repo unlikely SANDBOX_IDis used as the Docker image/container name (opencode-sandbox-<SANDBOX_ID>) and as the state directory name (~/.opencode-sandbox/<SANDBOX_ID>/)- Set automatically by
ocs-initusing the directory basename - You may rename it, but a rebuild is required and the old state directory in
~/.opencode-sandbox/will be orphaned
http-domain-whitelist β domains allowed through the Squid HTTP/HTTPS proxy:
- A leading dot matches the domain and all its subdomains (e.g.
.github.comallowsgithub.com,api.github.com,raw.githubusercontent.com, etc.) - Without a leading dot, only the exact domain is matched (e.g.
api.anthropic.comdoes not allowbedrock.anthropic.com) - When in doubt, use the leading-dot form to avoid hard-to-debug connection failures
host-ports β TCP ports on the host machine the container may connect to directly (bypasses the proxy):
- Use this for databases, local dev servers, and other services running on the host
- The host is reachable via
docker.host(injected automatically at container start) β use this hostname instead oflocalhost
env-passthrough β host environment variables to forward into the container:
- Format is
CONTAINER_VAR: HOST_VARβ use the same name on both sides for a simple passthrough, or different names to rename - Values are read from the host shell at container start time; variables not set on the host are skipped and noted in the startup summary
- Use this for secrets and credentials β values never touch a file
- A rebuild is required after adding or removing entries
env β static environment variables set directly in the container:
- Use this for non-secret project context that is safe to commit: repo name, project identifiers, feature flags, etc.
- Values are literal β no shell expansion
- A rebuild is required after adding or removing entries
ocs-rebuild-container reads this file to generate derived build artifacts β a rebuild is required after changes. The file is required; ocs-rebuild-container fails if it is missing.
If a file named opencode-sandbox-pre-start-container.sh exists in the project root, ocs-start-container will source it before starting the container. Because it is sourced (not executed as a subprocess), any export statements take effect in the calling shell and are picked up by env-passthrough.
Typical uses:
- Refresh short-lived credentials (AWS SSO, GCP, Azure, Vault, β¦)
- Derive env vars from the host at start time
ocs-init creates this file for you (empty, executable). If it contains secrets, add it to .gitignore:
opencode-sandbox-pre-start-container.sh
Example β refresh AWS SSO credentials and forward them into the container:
opencode-sandbox-pre-start-container.sh:
#!/usr/bin/env bash
aws sso login --profile my-profile
eval "$(aws configure export-credentials --profile my-profile --format env)"opencode-sandbox-config.yaml:
env-passthrough:
AWS_ACCESS_KEY_ID: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN: AWS_SESSION_TOKENopencode-sandbox/
βββ bin/
β βββ ocs-init # Initialize a project for use with opencode-sandbox
β βββ ocs-rebuild-container # Build the Docker image
β βββ ocs-start-container # Start / resume the container
β βββ ocs-terminal # Attach a terminal session
β βββ ocs-web # Open the web UI
β βββ ocs-web-auth # Open the web UI with authentication
βββ init-templates/ # Templates copied into target projects by ocs-init
βββ shared # Shared configuration, utilities, and guards (sourced by bin scripts)
βββ Dockerfile
βββ entrypoint.sh
βββ README.md
Each project gets its own isolated container named opencode-sandbox-<SANDBOX_ID>. The SANDBOX_ID is derived at runtime from the sandbox-name in the config file and a short hash of the project root path, so multiple checkouts of the same repo get distinct IDs. State is stored in ~/.opencode-sandbox/<SANDBOX_ID>/ on the host:
~/.opencode-sandbox/
βββ my-project-a3f92c/
βββ opencode-password # Generated server password (owner-only permissions)
βββ mise.toml # Copied from project root at build time
βββ squid.conf # Copied from opencode-sandbox repo at build time
βββ squid-whitelist.txt # Extracted from http-domain-whitelist at build time
βββ host-ports.txt # Extracted from host-ports at build time
βββ env-passthrough.txt # Extracted from env-passthrough at build time
βββ env.txt # Extracted from env at build time
βββ docker-build.log # Docker build output (created during build)
βββ opencode-state/ # Persistent OpenCode state (mounted into the container)
OpenCode state (including session history, configuration, and cache) is persisted across container restarts by mounting ~/.opencode-sandbox/<SANDBOX_ID>/opencode-state/ as /home/dev/.local/share/opencode inside the container.
Aside from the initial project setup files that ocs-init copies into the project root, opencode-sandbox writes its runtime/build artifacts and persistent sandbox state under ~/.opencode-sandbox/<SANDBOX_ID>/, not in the project directory β so no .gitignore entries are needed for sandbox state.
OpenCode Sandbox is not built by the OpenCode team and is not affiliated with OpenCode.