← Writing
6 min read

My OpenChamber setup on a VPS: persistent tools and sandboxed execution

How I run OpenChamber on a VPS, keep CLI tools across container updates, and use microsandbox to isolate agent workloads.

Agentic Development Environment
OpenCode
AI
Docker
microsandbox

My OpenChamber setup on a VPS: persistent tools and microVMs

I've been putting together an OpenChamber setup that I can use from both the browser and the desktop app. I run the container on a VPS, with my repositories stored there and mounted into it. I like having the coding environment on the server while still having a choice of how I work with OpenCode.

Keeping my tools between container rebuilds

Installing an extra CLI inside the running OpenChamber container is easy enough. The annoying part is that it disappears when I recreate the container, for example during an OpenChamber update. That's normal Docker behavior for files installed in the container's writable layer.

I store my extra tools in .tools rather than mounting over .npm-global, where the image already keeps OpenCode. This preserves my tools without interfering with OpenCode or its updates.

The Compose setup

For now, OpenChamber doesn't provide an official prebuilt container image, so I build it from the project's Dockerfile. That could change at any time, so it's worth checking the project before copying this setup. An official image could save you the build step.

I'm using OpenChamber v2.0.3 here. The same approach may work with other V2 releases, but check their Dockerfile and configuration before upgrading. If you're following along on a VPS, check KVM support before spending time on microsandbox: your provider needs to expose /dev/kvm and support nested virtualization if the VPS is itself a VM. OpenChamber can run without it, but these microVM workloads can't. Below is an example docker-compose.yml file for Docker Compose v2.

services:
  openchamber:
    build:
      context: https://github.com/openchamber/openchamber.git#v2.0.3
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      OPENCHAMBER_UI_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    ports:
      - "3000:3000"
    devices:
      - /dev/kvm:/dev/kvm
    group_add:
      - "REPLACE_WITH_YOUR_KVM_GROUP_ID"
    volumes:
      - ./data/openchamber:/home/openchamber/.config/openchamber
      - ./data/opencode/share:/home/openchamber/.local/share/opencode
      - ./data/opencode/state:/home/openchamber/.local/state/opencode
      - ./data/opencode/config:/home/openchamber/.config/opencode
      - ./data/tools:/home/openchamber/.tools
      - ./data/gh:/home/openchamber/.config/gh
      - ./data/ssh:/home/openchamber/.ssh
      - ./your/repositories/path:/home/openchamber/workspaces # Change this accordingly
    command:
      ["/bin/sh", "/home/openchamber/.config/opencode/entrypoint-tools.sh"]

Those mounts keep my OpenChamber and OpenCode data, extra tools, GitHub login, and SSH keys across container replacements. I mount the repositories separately so I can still work with them outside OpenChamber.

Replace the password, KVM group ID, and repository path before starting. You can find your KVM group ID by running this on your VPS:

stat -c '%g' /dev/kvm

Making the tools stick

I use a startup script to install any missing CLI tools in the persistent .tools directory. It adds /home/openchamber/.tools/bin to the front of PATH so OpenChamber can find them, and configures npm to install additional tools in .tools. I use it for GitHub CLI and OpenSpec, as well as microsandbox and its MCP server.

The wrapper exports:

export PATH="/home/openchamber/.tools/bin:$PATH"
export NPM_CONFIG_PREFIX=/home/openchamber/.tools
export MSB_HOME=/home/openchamber/.tools/msb-home

That last setting also saves me downloading cached sandbox images again after recreating the container. Guest data still needs explicit volumes or snapshots, though. A running microVM won't automatically survive the replacement.

Once the tools are ready, the wrapper hands startup back to OpenChamber's own entrypoint. I'd rather keep its normal startup behavior than maintain another launch command myself.

I've included a shorter, pinned version of my installer in entrypoint-tools.sh. The pins apply to fresh installations; existing tools are skipped, so changing a pin won't upgrade them automatically. It explicitly installs the matching microsandbox native package because, in my environment, the npm command shims were present even when the native runtime wasn't.

Why microsandbox is here

I wanted a bit more separation for the code I ask the agent to run. microsandbox gives me hardware-isolated microVMs for selected workloads, so I don't have to run all of that work directly in the application container.

OpenCode reaches it through a local MCP server. If you'd like to use the same integration, merge this into data/opencode/config/opencode.jsonc, keeping any settings you already have:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "microsandbox": {
        "type": "local",
        "command": ["/home/openchamber/.tools/bin/microsandbox-mcp"],
        "environment": {
          "MSB_HOME": "/home/openchamber/.tools/msb-home"
        }
      }
    }
  }
}

On Linux, /dev/kvm provides the virtualization interface, and group_add lets the non-root container user access it.

I use my project's AGENTS.md to tell the agent when to use microsandbox. Ordinary shell commands still run in the OpenChamber container, with access to its mounted repositories and credentials. These instructions guide the agent; they don't enforce sandboxing by themselves.

Here's an example for a Node.js project. Adapt the toolchain references to your own project:

## Execution environment

- In OpenChamber, use microsandbox for every test, script execution, and dev
  check. If microsandbox is unavailable, stop and ask instead of falling back
  to the ordinary shell.
- Outside OpenChamber, run commands directly with host tools, using the
  toolchain versions declared in `package.json` and `.nvmrc`.
- Automatically select the execution environment once per session, before
  executing project code, by running this check in the agent's ordinary shell
  (the container shell in OpenChamber):

  ```sh
  if [ -n "${OPENCHAMBER_AGENT_TOOL_URL:-}" ]; then
    printf '%s\n' microsandbox
  else
    printf '%s\n' host
  fi
  ```

- Use the result for all tests, scripts, and dev checks in that session.
  OpenChamber injects this marker into its managed OpenCode server's
  environment. Standalone sessions without the marker use host tools.
  Do not print the marker's value or any tokens.
  Do not infer the environment from the OS, working directory, available
  tools, or the presence of `msb`.
- This check assumes OpenChamber starts its own OpenCode server and that the
  shell inherits its environment. If using an external server, or if that
  assumption is uncertain, ask once which execution environment to use
  before running project code.

The marker is present in OpenChamber v2.0.3; it's a deployment-specific check, not a universal way to detect every OpenChamber connection.

Trying it on your own VPS and setting up gh

Download the companion wrapper and save it to data/opencode/config/entrypoint-tools.sh on your VPS before starting. Create the mounted directories and make sure they are writable by the pinned image's UID/GID 1000.

docker compose up -d --build
docker compose logs -f openchamber

Wait for the initial tool installation and application startup to finish, then exit the log view with Ctrl+C and sign in to GitHub:

docker compose exec openchamber /home/openchamber/.tools/bin/gh auth login

The absolute path matters here because a fresh docker compose exec doesn't inherit the wrapper's exported PATH.