Skip to content

Latest commit

Β 

History

History
208 lines (154 loc) Β· 8.71 KB

File metadata and controls

208 lines (154 loc) Β· 8.71 KB

Usage notes

Detailed operating notes for the opencode-dev-sanbox. The README covers the quick start; this page goes deeper on sudo, Nix, the CLI proxy, the OpenCode config, TLS and Chrome's sandbox.

Sudo

The account user is a member of the sudo group and escalation goes through the real /usr/bin/sudo:

sudo apt-get update

That prompts for the password you set on the first-run web form, and standard Debian sudo policy and timestamp caching apply.

  • The web password is the Unix password. There is one credential, not two.
  • Only the Unix shadow hash is persisted at /workspace/.devbox/user-password.hash, so the same login and sudo password survives container recreation as long as the workspace volume is kept.
  • Until first-run setup completes the account is locked, so sudo cannot be used at all.
  • There is no NOPASSWD sudoers drop-in and no passwordless escalation.
  • devbox-root and the fake sudo shim from earlier versions are gone.
  • The Cloud Run no_new_privs workaround is not supported and has no replacement. On a runtime that forbids setuid escalation, sudo will not work.

Nix

Nix runs single-user with no nix-daemon, and /nix is owned by uid 1000. Installing packages needs no sudo:

nix profile install nixpkgs#ripgrep
nix shell nixpkgs#gcc nixpkgs#cmake
nix develop

nix-command and flakes are enabled in /etc/nix/nix.conf.

Warning

The Nix store does not persist across container recreation. /nix lives in the container writable layer, not on the /workspace volume, so:

  • docker restart and docker stop/start keep everything you installed.
  • docker rm followed by a fresh docker run loses it. /workspace is unaffected.

State and cache are pinned to /nix/var/nix/user-state and /nix/var/nix/user-cache rather than $HOME, so a recreated container never leaves a profile symlinked to store paths that no longer exist.

Because of that, commit flake.nix and flake.lock with the project and let nix develop rebuild the environment. A per-project flake restores itself; a nix profile install does not.

Warning

Single-user Nix does not sandbox derivations (sandbox = false). Builds run with the same uid as the agent. That is fine for your own projects and not appropriate for untrusted derivations.

User-local Node packages

OpenCode and OpenChamber are installed as uid 1000 packages under the persistent workspace prefix:

/workspace/.local/bin
/workspace/.local/lib/node_modules

The managed service environment sets NPM_CONFIG_PREFIX=/workspace/.local and puts the prefix's bin directory first on PATH. This matches the package manager environment used by the web update controls, so updating OpenCode or OpenChamber does not require root or write access to /usr.

The image keeps a user-owned seed outside the workspace and copies it only when either application is missing from the volume. Existing user-installed versions are never overwritten. After updating OpenChamber from its web UI, restart the Agent application from the Launcher to load the new server process; the upstream container update flow intentionally keeps the current server online.

CLI Proxy

CLIProxyAPI brokers provider accounts behind one OpenAI-compatible endpoint, with the upstream Management Center panel baked into the image so it is available offline.

  1. Launch CLI Proxy from the dashboard.

  2. Open CLI Proxy from the dashboard β€” the new tab goes through a small bootstrap page that seeds your already-started opencode-dev-sanbox session into the panel, so there is no second login and no prompt for the management key.

  3. Add providers and create proxy API keys there. None are seeded for you.

  4. Point in-container agents and CLIs at the proxy:

    http://127.0.0.1:8317
    

If you need the management key for anything outside the browser, the entrypoint generates it on first start:

docker exec -u user devbox sh -c 'cat /workspace/.devbox/cliproxy/management.key'

Only the panel and its management API are reachable through the gateway. The /v1/ proxy surface is not published, so it stays loopback-only inside the container.

Provider sign-in supports device-code flows and manual callback-URL submission, plus plain API keys. No OAuth callback ports are published, so a browser on another machine cannot be redirected to a listener inside the container. Use device code or paste the callback URL into the panel.

State lives in /workspace/.devbox/cliproxy (mode 0700): config, management key, provider OAuth tokens, logs and plugins.

OpenCode configuration

OpenCode starts with a working global config, seeded on first boot to ~/.config/opencode/opencode.jsonc (that is /workspace/.config/opencode/opencode.jsonc, on the persistent volume). It enables LSP, web/code search, the context7 and chrome-devtools MCP servers, the background-agents and pty plugins, and registers Local CLIProxyAPI as a provider pointing at http://127.0.0.1:8317/v1.

Edit it like any other config β€” the entrypoint writes the file only when neither opencode.jsonc nor opencode.json exists there, so your changes are never overwritten by a restart or an image upgrade. To start over, delete the file and restart the container. The seed template ships at /etc/devbox/opencode.jsonc.

Note

The provider is configured but has no credentials. Add an account in the CLI Proxy Management Center and mint a proxy API key first, otherwise model calls fail with an auth error.

Custom hostname or LAN address in the certificate

Set extra SANs on first boot:

-e TLS_SAN="DNS:devbox.lan,IP:192.168.1.10"

Only read when the certificate does not exist yet. To regenerate, delete /workspace/.devbox/tls and restart the container. You can also drop your own devbox.crt / devbox.key in that directory.

Environment variables

Variable Default Effect
TLS_SAN β€” Extra SANs for the generated certificate

Chrome sandbox

Chrome's renderer sandbox creates an unprivileged user namespace. Docker's default seccomp profile blocks that operation, so the quick start uses seccomp=unconfined. The container still receives no host devices, host namespaces, host socket, or host-level capabilities.

At boot the image probes unshare -Ur. If it is unavailable, Chrome starts with --no-sandbox rather than failing silently. This reduces browser defense in depth; use the quick-start setting when the desktop browser is exposed to untrusted web content.

If the probe fails despite the seccomp setting, the host may disallow unprivileged user namespaces:

sudo sysctl -w kernel.unprivileged_userns_clone=1

Recent Ubuntu hosts can additionally restrict unprivileged user namespaces through AppArmor:

sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

Caution

These are host-wide settings; evaluate them against your threat model before changing them.

Ports

The public nginx gateway listens on 80 (plaintext HTTP) and 443 (self-signed TLS). Everything else binds loopback only:

Service Internal endpoint Public route
Agent (OpenChamber) 127.0.0.1:9100 /
Code (code-server) 127.0.0.1:9101 /code/
Control API (devbox-api) 127.0.0.1:9102 /launcher/api/
Desktop bridge (websockify) 127.0.0.1:9103 /vnc/
Files (FileBrowser) 127.0.0.1:9104 /files/
Web terminal broker 127.0.0.1:9105 /terminal/api/, /terminal/ws/
CLI Proxy (CLIProxyAPI) 127.0.0.1:8317 /management.html only

Internal services sit at 9100–9105, so dev servers you run inside the box (3000, 5173, 8080, …) never collide. CLIProxyAPI is the exception at 8317, outside that range: it keeps the upstream default port so provider documentation and agent configs work unchanged.

Web terminal sessions

The Terminal application stores each tab as a private tmux session. A browser WebSocket is only an attachment to that session: closing the tab, closing the browser or losing the network does not terminate the shell or commands running inside it. Reopen the Terminal application to enumerate existing sessions and reconnect automatically.

The Kill action is separate from detaching a tab and terminates the tmux session. Sessions and their processes cannot survive docker restart or container recreation because they are runtime processes, not files on /workspace.